赞
踩
通过上几篇文章,我们已经了解了什么是顺序表和单链表,具体可以点进去我的主页看!包括静态顺序表和动态顺序表,单链表。下面我们来实现一种新的链表---带头双向循环链表!
目录
节点共有3个空间,一个位置存数据,一个位置存指向前一个节点的指针,一个存指向下一个节点的指针。
注意:头结点我们不存数据,如果数据类型是char或者double之类的类型,不能存链表的长度。
我们在使用单链表时,经常要找到前一个节点。如果要找到尾节点,要遍历链表才能找到。
单链表的尾插尾删时间复杂度都是O(N) ,若我们使用的是双向链表,头结点中指向上一个节点的指针指向的就是尾结点,进行尾插尾删很简单。
双向循环链表的结构是在双向链表的基础上使头节点的前驱指针指向末尾的节点,而使末尾的节点的一个指针指向开始节点,形成一个循环结构。
工程文件 | 存放的内容 |
---|---|
List.h | 函数声明,宏定义 |
List.c | 实现顺序表函数接口的定义 |
test.c | 测试函数接口是否能实现功能 |
为了后续我们想更改数据类型时方便,我们一般对类型进行typedef操作。
- typedef int ListType;
- typedef struct List
- {
- ListType data;//存放数据
- struct List* next;//指向下一个节点
- struct List* prev;//指向上一个节点
- }List;
若我们的头结点选择的是结构体,就需要对结构体初始化。若我们选择的头结点是以指针方式,就要开辟一个结构体空间,该指针指向该空间,充当头结点。
- //初始化哨兵位
-
- //哨兵位为结构体:
- void ListInit1(List* phead)
- {
- //最初prev和next都指向自己
- phead->data = 0;
- phead->next = phead;
- phead->prev = phead;
- }
-
- //哨兵位为指针->malloc一个结构体
- List* ListInit2()
- {
- List* newnode = (List*)malloc(sizeof(List));
- if (newnode == NULL)
- {
- printf("malloc is fail\n");
- return NULL;
- }
- else
- {
- //最初prev和next都指向自己
- newnode->data = 0;
- newnode->prev = newnode;
- newnode->next = newnode;
- return newnode;
- }
- }
注意:我们最开始初始化头结点时前驱指针和后继指针都指向自己,(后续就能看出有什么好处)
遍历链表打印数据即可。当指针指向的是头结点时跳出循环。
注意:phead是头结点(哨兵位),phead->next才是第一个结点。
- //打印
- void ListPrint(List* phead)
- {
- List* cur = phead->next;//第一个节点
- //最终回到phead
- while (cur != phead)
- {
- printf("%d ->", cur->data);
- cur = cur->next;
- }
- printf("NULL\n");
- }
使用malloc动态开辟一个空间,要判断空间是否开辟成功!新节点的前驱指针和后继指针置空
- //创建新节点
- List *BuyNewnode(ListType x)
- {
- List* newnode = (List*)malloc(sizeof(List));
- if (newnode == NULL)
- {
- printf("malloc is fail\n");
- return NULL;
- }
- else
- {
- newnode->data = x;
- newnode->next = NULL;
- newnode->prev = NULL;
- return newnode;
- }
- }
步骤:
1.动态开辟一个新节点newnode
2.原来的尾节点的next链接newnode
3.newnode的前驱指针指向原来的尾节点,newnode的后继指针指向头结点phead
4.头结点的前驱指针指向newnode
注:phead->prev就是尾节点
- //尾插
- void ListPushBack(List* phead, ListType x)
- {
- assert(phead);
- List* newnode = BuyNewnode(x);
- List* tail = phead->prev; //phead->prev即为尾节点
- tail->next = newnode; //尾节点的next链接新节点
- newnode->prev = tail; //新节点的前指针链接原来的尾节点
- newnode->next = phead; //新节点的后指针链接哨兵位
- phead->prev = newnode; // 哨兵位的前指针指向新节点
- }
步骤:
1.动态开辟新节点newnode
2.保存第一个节点first(phead->next就是第一个节点)
3. phead newnode first 三者之间进行链接
- //头插
- void ListPushFront(List* phead, ListType x)
- {
- assert(phead);
- List* newnode = BuyNewnode(x);
- List* first = phead->next; //第一个节点
- //phead newnode first进行链接
- phead->next = newnode;
- newnode->prev = phead;
- newnode->next = first;
- first->prev = newnode;
- }
步骤:
1.注意先判断链表是否为空,如果链表为空就不删除了
2.保存倒数第二个节点 ,释放尾节点空间。
3.倒数第二个节点和头节点进行链接
倒数第二个节点的next指向头节点,头节点的prev指向倒数第二个节点。
- //尾删
- void ListPopBack(List* phead)
- {
- //若链表为空,按下面写法,就把哨兵位free掉了,所以判断一下
- if (phead->prev == phead)
- {
- printf("链表为空\n");
- return;
- }
- else
- {
- //释放尾节点 +保存倒数第二个节点
- List* tail = phead->prev;//尾节点
- List* prev = tail->prev;//倒数第二个节点
- free(tail);
- prev->next = phead;;
- phead->prev = prev;
- }
- }
步骤:
1.注意先判断链表是否为空,如果链表为空就不删除了
2.保存第二个节点,释放第一个节点
3.第二个节点和头节点进行链接
第二个节点的prev指向头节点,头节点的next指向第二个节点
- //头删
- void ListPopFront(List* phead)
- {
- //若链表为空,按下面写法,就把哨兵位free掉了,所以判断一下
- if (phead->prev == phead)
- {
- printf("链表为空\n");
- return;
- }
- else
- {
- //找到第二个节点
- List* first = phead->next;
- List* second = first->next;
- free(first);
- phead->next = second;
- second->prev = phead;
- }
-
- }
遍历查找,找到了返回所在的位置。找不到返回NULL。
- //查找元素,返回对应的地址
- List* ListFind(List* phead, int x)
- {
- //遍历
- List* cur = phead->next;
- while (cur != phead)
- //我们定义时 cur指向的是第一个节点
- //不可写成cur->next !=phead,若只有一个节点,且数据在第一个节点就err了
- {
- if (cur->data == x)
- {
- return cur;
- }
- cur = cur->next;
- }
- //找不到
- return NULL;
- }
步骤:
1.动态开辟一个节点
2.找到pos位置前一个节点 prev
3.prev newnode pos进行链接
- //在某位置前插入数据
- void ListInsert(List* pos, ListType x)
- {
- List* newnode = BuyNewnode(x);
- //pos前一个位置
- List* prev = pos->prev;
- //prev newnode pos
- prev->next = newnode;
- newnode->prev = prev;
- newnode->next = pos;
- pos->prev = newnode;
- }
步骤:
1.保存pos前的节点 和 pos后的节点
2.释放pos指向的节点
3.pos前的节点和pos后的节点进行链接
- //删除某个节点
- void ListErase(List* pos)
- {
- List* prev = pos->prev;//pos前的节点
- List* next = pos->next;//pos后的节点
- free(pos);
- prev->next = next;
- next->prev = prev;
- }
我们最开始定义头节点的next是指向自己的,如果现在还是指向自己,说明链表为空
- //链表是否为空
- bool ListEmpty(List* phead)
- {
- return phead->next == phead;
- }
使用计数器遍历计数
- //计算链表长度
- int ListLong(List* phead)
- {
- //遍历+计数器
- List* cur = phead->next;
- int count = 0;
- while (cur != phead)
- {
- count++;
- cur = cur->next;
- }
- return count;
- }
遍历释放空间,要先保存下一个节点再释放。不能反过来,否则就找不到下一个节点。
- //销毁
- //我们定义的哨兵位是结构体,,不是结构体指针 所以不用置空也不用free
- //free:动态开辟的内容
- void ListDestory(List* phead)
- {
- //遍历销毁,
- List* cur = phead->next;
- while (cur != phead)
- {
- List* next = cur->next;
- free(cur);
- cur = next;
- }
-
- }
1.phead是头节点并不算进链表内容,当插入元素后,phead->next指向的才是第一个节点。
2.双向链表结构复杂但是实现起来很简单。因为双向链表有两个指针,一个指向前一个结点,一个指向后一个节点。相比于单链表,很容易就找到它的前一个节点。
如何快速写完双向链表:
只写上面的Erase和Insert代码
- //在某位置前插入数据
- void ListInsert(List* pos, ListType x)
- {
- List* newnode = BuyNewnode(x);
- //pos前一个位置
- List* prev = pos->prev;
- //prev newnode pos
- prev->next = newnode;
- newnode->prev = prev;
- newnode->next = pos;
- pos->prev = newnode;
- }
-
- //删除某个节点
- void ListErase(List* pos)
- {
- List* prev = pos->prev;//pos前的节点
- List* next = pos->next;//pos后的节点
- free(pos);
- prev->next = next;
- next->prev = prev;
- }
- //尾插
- void ListPushBack(List* phead, ListType x)
- {
- assert(phead);
- //可以认为在phead前面插入
- ListInsert(phead, x);
- }
-
- //头插
- void ListPushFront(List* phead, ListType x)
- {
- assert(phead);
- //在第一个节点后面插入
- ListInsert(phead->next, x);
- }
-
- //尾删
- void ListPopBack(List* phead)
- {
- assert(phead);
- //防止链表尾空
- if (phead->next == phead)
- {
- return;
- }
- //删的是phead->prev
- ListDestory(phead->prev);
- }
-
- //头删
- void ListPopFront(List* phead)
- {
- assert(phead);
- //防止链表尾空
- if (phead->next == phead)
- {
- return;
- }
- ListDestory(phead->next);
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。