赞
踩
目录
一、不带头单链表
初始准备
链表节点创建与销毁
尾插尾删
头插头删
随机位置插入与删除
缺陷
全部代码
二、链表带环问题
三、带头双链表
初始准备
链表节点创建与销毁
尾插尾删
头插头删
随机位置插入与删除
全部代码
四、对比顺序表与链表
一、不带头单链表
初始准备
不带头单链表,首先创建了一个结构体,而该结构体有2个成员,一个用来存储数据(data),另一个用来存储下一个数据的地址(next*),从而使得两个结构体变量连接起来,如下图
为了书写简洁,用typedef将其结构体类型名修改
又为了能方便存储不同类型的数据,不至于把程序写的太呆板,采用typedef将其类型名修改,要存储其他类型的数据时,只要修改即可。如下图,存储浮点型数据时,只要int改为float或double
打印链表
每打印一个数据,指针就向后移动一步
注意:打印因为没有改变链表,所以只需传一级指针即可
上图也可以不用创建cur,直接用phead
指针传参
无头单链表中,只有打印和查找数据时,不用传二级指针,其余时候都要传二级,因为要改变链表
上述两种都是传递地址,在C语言中要改变实参时,必须传地址才行
链表节点创建与销毁
先用malloc开辟一块空间,大小是结构体的大小,然后再做判断,最后再对成员进行初始化即可
链表销毁时,要采用两个指针,一个在前,一个在后,要不然释放空间后,就成了野指针,找不到后面的数据了,把最后一个数据销毁后,再将其置空即可
尾插尾删
首先判断链表是否为空(没有数据),没有就直接添加数据即可,有就找尾,最后一个数据的next指针为空,就表示找到了,直接连接数据紧接其后即可
要分两种情况,一种是只有一个节点时,另一种是多个节点时
多个节点时,尾删也需要两个指针,否则在删除最后一个数据后,要把前一个数据的next*置空,否则next*就是野指针了,下一次删除就找不到尾了
一个节点时,直接释放,置空即可
头插头删
头插时不需要判断链表是否为空,只要创建新节点,让它指向头节点,再把新节点的地址赋值给头节点的指针,作为新的头节点指针
头删时要判断链表是否为空,毕竟空链表不能删除
先用一个指针变量存储头节点的下一个节点的地址,再将头节点销毁,再将其前面创建的指针的值赋给头节点的指针
随机位置插入与删除
查找随机数,返回其地址,找不到就返回空指针,同样不需要传二级指针
在随机数的后面插入数据,找不到数据时,pos为空,所以断言判断一下。如下图,如果不多创建一个指针变量时,就需要先连线2,再连线1,因为先练线1时,就找不到cur2,指向的那个节点了;多创建一个变量保存节点地址,1、2那个先连都可以
在随机数的前面插入数据时,要分两种情况
第一种,随机数就是头节点的数据时,直接头插即可
第二种,找到pos的前一个节点,再创建节点,3个节点连接即可
在前面和后面插入两种方法对比,在后面插入明显更简单,效率也更高,因为不需要找前一个节点,这也是单链表的缺陷,双链表可以弥补这一缺陷
删除随机数后面的一节点的数据时,要作2次断言,第一个是随机数存不存在,第二个是随机数的下一个节点存不存在
删除随机数时,首先判断是不是随机数存不存在,同样有两种情况
第一种,随机数是头节点的数据时,直接头删即可
第二种,找到随机数的前一个节点,再连接,释放随机数的节点即可
缺陷:
每存一个数据,都要存一个指针去链接后面数据节点
不支持随机访问(用下标去访问第i个)
全部代码
- //头文件
- #pragma once
- #include<stdio.h>
- #include<stdlib.h>
- #include<assert.h>
-
- typedef int DataType;
-
- typedef struct SList
- {
- DataType data;
- struct SList* next;
- }SLT;
-
- //创建节点
- SLT* SListNode(DataType x);
- //打印链表
- void SListPrint(SLT* phead);
- //销毁链表
- void SListDestroy(SLT** pphead);
- //尾插
- void SListPushBack(SLT** pphead, DataType x);
- //尾删
- void SListPopBack(SLT** pphead);
- //头插
- void SListPushFront(SLT** pphead, DataType x);
- //头删
- void SListPopFront(SLT** pphead);
- //查找随机数
- SLT* SListFind(SLT* pphead, DataType pos);
- //随机位置向后插入
- void SListInsert(SLT* pos, DataType x);
- //随机位置的数的删除
- void SListDeleteAfter(SLT* pos);
- //随机位置向前插入
- void SListInsertPrev(SLT** pphead, SLT* pos, DataType x);
- //随机位置数据的删除
- void SListDelete(SLT** pphead, SLT* pos);
-
- //定义文件
- #include"SList.h"
-
- SLT* SListNode(DataType x)
- {
- SLT* newnode = (SLT*)malloc(sizeof(SLT));
- if (newnode == NULL)
- {
- printf("malloc fail\n");
- exit(-1);
- }
- newnode->data = x;
- newnode->next = NULL;
- return newnode;
- }
-
- void SListPushBack(SLT** pphead, DataType x)
- {
- if (*pphead == NULL)
- {
- *pphead = SListNode(x);
- }
- else
- {
- SLT* cur = *pphead;
- while (cur->next)
- {
- cur = cur->next;
- }
- cur->next = SListNode(x);
- }
- }
-
- void SListPrint(SLT* phead)
- {
- SLT* cur = phead;
- while (cur)
- {
- printf("%d ", cur->data);
- cur = cur->next;
- }
- printf("\n");
- }
-
- void SListDestroy(SLT** pphead)
- {
- assert(pphead);
- SLT* cur = *pphead;
- SLT* prev = *pphead;
- while (cur)
- {
- prev = cur;
- cur = cur->next;
- free(prev);
- }
- *pphead = NULL;
- }
-
- void SListPopBack(SLT** pphead)
- {
- assert(*pphead);
- SLT* cur = *pphead;
- SLT* prev = NULL;
- if ((*pphead)->next == NULL)
- {
- free(*pphead);
- *pphead = NULL;
- }
- else
- {
- while (cur->next)
- {
- prev = cur;
- cur = cur->next;
- }
- free(cur);
- cur = NULL;
- prev->next = NULL;
- }
- }
-
- void SListPushFront(SLT** pphead, DataType x)
- {
- SLT* cur = *pphead;
- SLT* newnode = SListNode(x);
- newnode->next = cur;
- *pphead = newnode;
- }
-
- void SListPopFront(SLT** pphead)
- {
- assert(*pphead);
- SLT* cur = (*pphead)->next;
- free(*pphead);
- *pphead = cur;
- }
-
- SLT* SListFind(SLT* phead, DataType pos)
- {
- assert(phead);
- SLT* cur = phead;
- while (cur)
- {
- if (cur->data == pos)
- {
- return cur;
- }
- else
- {
- cur = cur->next;
- }
- }
- return NULL;
- }
-
- void SListInsert(SLT* pos, DataType x)
- {
- assert(pos);
- SLT* cur2 = pos->next;
- SLT* newnode = SListNode(x);
- newnode->next = cur2;
- pos->next = newnode;
- }
-
- void SListDelete(SLT** pphead, SLT* pos)
- {
- assert(pphead);
- assert(pos);
- if (pos == *pphead)
- {
- SListPopFront(pphead);
- }
- else
- {
- SLT* prev = *pphead;
- while (prev->next != pos)
- {
- prev = prev->next;
- }
- prev->next = pos->next;
- free(pos);
- pos = NULL;
- }
- }
- void SListDeleteAfter(SLT* pos)
- {
- assert(pos);
- assert(pos->next);
- pos->next = pos->next->next;
- free(pos->next);
- pos->next = NULL;
- }
-
- void SListInsertPrev(SLT** pphead, SLT* pos, DataType x)
- {
- SLT* newnode = SListNode(x);
- if (*pphead == pos)
- {
- newnode->next = *pphead;
- *pphead = newnode;
- }
- else
- {
- SLT* prev = *pphead;
- while (prev->next != pos)
- {
- prev = prev->next;
- }
- newnode->next = pos;
- prev->next = newnode;
- }
- }
-
- //测试文件
- #include"SList.h"
- void test1()
- {
- SLT* plist = NULL;
- SListPushBack(&plist, 1);
- SListPushBack(&plist, 2);
- SListPushBack(&plist, 3);
- SListPushBack(&plist, 4);
- SListPrint(plist);
-
- SListPopBack(&plist);
- SListPopBack(&plist);
- SListPopBack(&plist);
- SListPopBack(&plist);
- SListPopBack(&plist);
- SListPopBack(&plist);
- SListPopBack(&plist);
-
- SListPrint(plist);
- }
- void test2()
- {
- SLT* plist = NULL;
- SListPushFront(&plist, 1);
- SListPushFront(&plist, 2);
- SListPushFront(&plist, 3);
- SListPushFront(&plist, 4);
- SListPrint(plist);
-
- SListPopFront(&plist);
- SListPopFront(&plist);
- SListPopFront(&plist);
- SListPopFront(&plist);
- SListPrint(plist);
-
- }
- void test3()
- {
- SLT* plist = NULL;
- SListPushBack(&plist, 1);
- SListPushBack(&plist, 2);
- SListPushBack(&plist, 3);
- SListPushBack(&plist, 4);
-
- SLT* pos = SListFind(plist, 3);
- SListInsert(pos, 8);
-
- /*SListDeleteAfter(pos);
- SListPrint(plist);*/
-
- SListInsertPrev(&plist, pos, 15);
- SListPrint(plist);
-
- SListDelete(&plist, pos);
- SListPrint(plist);
-
-
- SListDestroy(&plist);
- }
- int main()
- {
- //test1();
- //test2();
- test3();
- return 0;
- }
二、链表带环问题
结论一:
快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表起始位置开始运行,如果链表 带环则一定会在环中相遇
证明如下图:
至于fast走4步,slow走1步的情况下,读者可依照上图依葫芦画瓢分析
结论二:
fast与slow一定会在环的入口处相遇
三、带头双链表
初始准备
首先创建一个结构体,有3个成员,第一个是存储数据,2个指针,一个指向前节点,另一个指向后节点,用typedef将其结构体类型名修改,使其简洁,方便使用
用typedef将其数据类型名修改,便于存储其它数据时,不用大幅度改动
链表节点创建与销毁
首先创建一个头节点,用malloc开辟空间,不存储数据,前后指针均指向本身
然后用malloc创建其它节点,添加节点时,只需调用函数即可,前后指针均置为NULL
销毁双链表时,从头节点后的节点开始销毁,最后再销毁头节点
打印双链表
尾插尾删
尾删不能把头节点给删了,所以需断言
头插头删
头删不能把头节点给删了,所以需断言
随机位置插入与删除
查找
找不到返回NULL,找得到返回其地址
全部代码
- //头文件
- #pragma once
- #include<stdio.h>
- #include<stdlib.h>
- #include<assert.h>
- typedef int DataType;
-
- typedef struct List
- {
- DataType data;
- struct List* prev;
- struct List* next;
- }LT;
-
- //初始化双链表
- LT* LTInit();
- //创建节点
- LT* LTNode(DataType x);
- //打印双链表
- void LTPrint(LT* phead);
- //尾插
- void LTPushBack(LT* phead, DataType x);
- //尾删
- void LTPopBack(LT* phead);
- //头插
- void LTPushFront(LT* phead, DataType x);
- //头删
- void LTPopFront(LT* phead);
- //查找
- LT* LTFind(LT* phead, DataType pos);
- //随机位置插入数据
- void LTInsert(LT* pos, DataType x);
- //随机位置的数据删除
- void LTDelete(LT* pos);
- //销毁双链表
- void LTDestroy(LT* phead);
-
- //定义文件
- #include"List.h"
-
- LT* LTInit()
- {
- LT* head = (LT*)malloc(sizeof(LT));
- if (head == NULL)
- {
- printf("malloc fail\n");
- exit(-1);
- }
- head->next = head;
- head->prev = head;
- return head;
- }
-
- LT* LTNode(DataType x)
- {
- LT* newnode = (LT*)malloc(sizeof(LT));
- if (newnode == NULL)
- {
- printf("malloc fail\n");
- exit(-1);
- }
- newnode->data = x;
- newnode->prev = NULL;
- newnode->next = NULL;
- return newnode;
- }
-
- void LTPrint(LT* phead)
- {
- assert(phead);
- LT* cur = phead->next;
- while (cur != phead)
- {
- printf("%d ", cur->data);
- cur = cur->next;
- }
- printf("\n");
- }
-
- void LTPushBack(LT* phead, DataType x)
- {
- assert(phead);
- LT* newnode = LTNode(x);
- LT* tail = phead->prev;
- tail->next = newnode;
- newnode->prev = tail;
- newnode->next = phead;
- phead->prev = newnode;
- }
-
- void LTPopBack(LT* phead)
- {
- assert(phead);
- assert(phead->next != phead);
- LT* tail = phead->prev;
- LT* prevtail = phead->prev->prev;
- phead->prev = prevtail;
- prevtail->next = phead;
- free(tail);
- tail = NULL;
- }
-
- void LTPushFront(LT* phead, DataType x)
- {
- assert(phead);
- LT* newnode = LTNode(x);
- LT* begintail = phead->next;
- phead->next = newnode;
- newnode->prev = phead;
- begintail->prev = newnode;
- newnode->next = begintail;
- }
-
- void LTPopFront(LT* phead)
- {
- assert(phead);
- assert(phead->next != phead);
- LT* begin = phead->next;
- LT* cur = begin->next;
- phead->next = cur;
- cur->prev = phead;
- free(begin);
- begin = NULL;
- }
-
- LT* LTFind(LT* phead, DataType pos)
- {
- assert(phead);
- LT* cur = phead->next;
- while (cur != phead)
- {
- if (cur->data == pos)
- {
- return cur;
- }
- else
- {
- cur = cur->next;
- }
- }
- return NULL;
- }
-
- void LTInsert(LT* pos, DataType x)
- {
- assert(pos);
- LT* newnode = LTNode(x);
- LT* prevpos = pos->prev;
- prevpos->next = newnode;
- newnode->prev = prevpos;
- pos->prev = newnode;
- newnode->next = pos;
- }
-
- void LTDelete(LT* pos)
- {
- assert(pos);
- LT* prevpos = pos->prev;
- LT* tailpos = pos->next;
- prevpos->next = tailpos;
- tailpos->prev = prevpos;
- free(pos);
- pos = NULL;
- }
-
- void LTDestroy(LT* phead)
- {
- assert(phead);
- LT* cur = phead->next;
- while (cur != phead)
- {
- LT* next = cur->next;
- free(cur);
- cur = next;
- }
- free(phead);
- phead = NULL;
- }
-
- //测试文件
- #include"List.h"
- void test1()
- {
- LT* plist = LTInit();
- LTPushBack(plist, 1);
- LTPushBack(plist, 2);
- LTPushBack(plist, 3);
- LTPushBack(plist, 4);
- LTPushBack(plist, 5);
- LTPushBack(plist, 6);
- LTPushBack(plist, 7);
- LTPrint(plist);
-
- LTPopBack(plist);
- LTPopBack(plist);
- LTPopBack(plist);
- LTPopBack(plist);
- //LTPopBack(plist);
- //LTPopBack(plist);
- //LTPopBack(plist);
- //LTPopBack(plist);
-
- LTPrint(plist);
- }
- void test2()
- {
- LT* plist = LTInit();
- LTPushFront(plist, 1);
- LTPushFront(plist, 2);
- LTPushFront(plist, 3);
- LTPushFront(plist, 4);
- LTPushFront(plist, 5);
- LTPushFront(plist, 6);
- LTPushFront(plist, 7);
- LTPrint(plist);
-
- LTPopFront(plist);
- LTPopFront(plist);
- LTPopFront(plist);
- LTPopFront(plist);
- LTPopFront(plist);
- //LTPopFront(plist);
- //LTPopFront(plist);
- //LTPopFront(plist);
-
- LTPrint(plist);
- }
- void test3()
- {
- LT* plist = LTInit();
- LTPushBack(plist, 1);
- LTPushBack(plist, 2);
- LTPushBack(plist, 3);
- LTPushBack(plist, 4);
-
- LT* pos = LTFind(plist, 3);
- LTInsert(pos, 8);
- LTInsert(pos, 16);
-
- LTDelete(pos);
- LTPrint(plist);
- LTDestroy(plist);
- }
- int main()
- {
- test1();
- //test2();
- //test3();
- return 0;
- }
四、对比顺序表与链表
顺序表
优点:
支持随机访问。需要随机访问结构支持算法可以很好的适用
缺点:
头部中部插入删除时间效率低 O(n)
连续的物理空间,空间不够了以后需要增容:
增容有一定程度消耗
为了避免频繁增容,一般我们都按倍数去增容,用不完可能存在一定的空间浪费
链表(双向带头循环)
优点:
任意位置插入删除效率高 O(1)
按需申请释放空间
缺点:
不支持随机访问(用下标访问)。意味着:一些排序、二分查找等在这种结构上不适用
链表每存储一个值,同时要存储一个指针,也有一定的消耗
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。