【数据结构】线性表--链表(二)
【数据结构】线性表--链表(二)
- 一.前情回顾
- 二.知识补充
- 1.单向链表,双向链表
- 2.带头结点,不带头结点
- 3.循环链表,不循环链表
- 三.带头双向循环链表的实现
- 1.申请新结点:
- 2.链表初始化:
- 3.尾插函数:
- 4.头插函数:
- 5.头删函数:
- 6.尾删函数:
- 7.查找函数:
- 8.在pos结点前插入函数:
- 9.删除pos结点的值函数:
- 10.判空函数:
- 11.销毁函数:
- 四.总结
- 1.头文件(声明链表的结构,操作等,起到目录作用):
- 2.源文件(具体实现各种操作):
- 3.测试文件(对各个函数功能进行测试):
一.前情回顾
上篇文章主要讲述了单链表的特点及各种增删查改等各种操作,我们会发现单链表在某些操作中需要寻找前驱结点,然而单链表中并没有直接指向前驱结点的指针,这样使得有些操作会有点麻烦,因此可以增加一个指向前驱结点的指针域,成为双链表。
二.知识补充
同时我们可以在链表的第一个结点前增加一个哨兵位的头结点,为了操作的统一和方便而设立。它的数据域并不保存实际的有效数据,指针域指向链表的第一个结点。
除此之外,链表还可分为循环链表和不循环链表。循环链表的最后一个结点又指向头结点,不循环链表的尾结点指向空。
因此链表总共可分为三类:
1.单向链表,双向链表
2.带头结点,不带头结点
3.循环链表,不循环链表
所以以上三类可以任意结合出来八种链表,上篇文章讲述的单链表具体是不带头单向不循环链表。
本篇文章则要讲述带头双向循环链表。
三.带头双向循环链表的实现
tips:涉及增删查改的各种操作时,可以自己画图看看如何修改各个指针的指向,便于理解。
1.申请新结点:
申请新结点即先用malloc函数申请一块结点大小的空间,然后将数值赋给数值域,指针都置为空,然后将新结点返回。
malloc函数不清楚的可以去看我之前的C语言动态内存管理那篇文章: https://blog.csdn.net/2401_85032912/article/details/142744593?spm=1001.2014.3001.5501)
//申请新结点
ListNode* BuyListNode(LTDataType x)
{ListNode* NewNode = (ListNode*)malloc(sizeof(ListNode));NewNode->data = x;NewNode->prev = NULL;NewNode->next = NULL;return NewNode;
}
2.链表初始化:
链表初始化需要先申请一个头结点,然后将头结点的prev指针和next指针均指向自己(因为是双向链表),数值域可以给一个不具有实际意义的值。
初始化方式有两种,第一种是通过传二级指针修改头结点;第二种是直接申请新结点,修改完之后作为返回值返回。
//初始化函数
ListNode* ListInit()
{//初始链表为空时,需将头结点的prev指针和next指针都指向自己ListNode* phead = BuyListNode(-1);phead->next = phead->prev = phead;return phead;
}
//或:
/*
void ListInit(ListNode** pphead)
{//初始链表为空时,需将头结点的prev指针和next指针都指向自己*pphead = BuyListNode(-1);(*pphead)->next = (*pphead)->prev = *pphead;
}
*/
3.尾插函数:
因为可以通过头结点找到第一个有效结点,所以不需要传二级指针(后续操作同理)。
双向链表尾插时不需要从头遍历到尾结点,因为头结点的prev指针即指向最后一个结点。
如图:
因此在尾插时,只需要通过头结点找到最后一个结点,将新结点的prev指针指向最后一个结点;next指针指向头结点;让最后一个结点的next指针指向新的结点;将头结点的prev指针指向新的结点。
//尾插函数
void ListPushBack(ListNode* phead, LTDataType x)
{assert(phead); ListNode* newnode = BuyListNode(x);ListNode* tail = phead->prev;//找到最后一个结点newnode->prev = tail;//新结点的prev指向最后一个结点newnode->next = phead;//新结点的next指向头结点tail->next = newnode;//最后一个结点的next指向新结点phead->prev = newnode;//头结点的prev指向新结点//此时新结点成为当前链表的最后一个结点
}
此时新结点就变成最后一个结点。
4.头插函数:
头插时先找到第一个结点,将新结点的next指针指向第一个结点,prev指针指向头结点,再第一个结点的next指针指向修改为新结点,最后修改头结点的next指针指向的结点。
//头插函数
void ListPushFront(ListNode* phead, LTDataType x)
{assert(phead);ListNode* newnode = BuyListNode(x);ListNode* first = phead->next;//找到第一个结点newnode->next = first;newnode->prev = phead;first->prev = newnode;phead->next = newnode;
}
因为将第一个结点找出来了,所以不需要考虑修改指针的先后顺序,否则需要考虑顺序问题。
5.头删函数:
头删时注意链表为空时不能删除。不为空时,找出第一个结点和第二个结点,将头结点的next指针指向第二个结点,将第二个结点的prev指针指向头结点,然后释放第一个结点指向的空间,指针置为空。
//头删函数
void ListPopFront(ListNode* phead)
{assert(phead);//链表为空时不能删除(或者使用断言)if (phead->next == phead)return;else{ListNode* first = phead->next;//找到第一个结点ListNode* second = first->next;//找到第二个结点phead->next = second;second->prev = phead;free(first);first = NULL;}
}
6.尾删函数:
链表为空时同样不能删除。不为空时找出最后一个结点和倒数第二个结点,将头结点的prev指针指向倒数第二个结点,倒数第二个结点的next指针指向头结点,释放最后一个结点,并置为空。
//尾删函数
void ListPopBack(ListNode* phead)
{assert(phead);//链表为空时不能删除(或者使用断言)if (phead->next == phead)return;else{ListNode* tail = phead->prev;//找到最后一个结点ListNode* prev = tail->prev;//找到倒数第二个结点phead->prev = prev;prev->next = phead;free(tail);tail = NULL;}
}
7.查找函数:
链表为空时直接返回NULL,链表不为空时,从第一个结点开始遍历到最后一个结点,若结点的值与查找的值相等,返回该结点,否则返回NULL。
ListNode* ListFind(ListNode* phead, LTDataType x)
{assert(phead);//链表为空时,返回NULLif (phead->next == phead)return NULL;else{ListNode* cur = phead->next;while (cur != phead){if (cur->data == x)return cur;elsecur = cur->next;}return NULL;}
}
8.在pos结点前插入函数:
首先申请一个新结点,然后找到pos结点的前一个结点,将前一个结点的next指针指向新结点,pos结点的prev指针也指向新结点,新结点的prev指针指向前一个结点,next指针指向pos结点。
//在pos位置前插入结点
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* newnode = BuyListNode(x);ListNode* prev = pos->prev;//找到前一个结点prev->next = newnode;pos->prev = newnode;newnode->prev = prev;newnode->next = pos;
}
实现完该函数之后,头插、尾插函数可以直接复用该函数。
头插即在头结点的next位置插入,可以直接写成ListInsert(phead->next, x);
。
尾插即在头结点前插入,可以直接写成ListInsert(phead, x);
。
9.删除pos结点的值函数:
删除pos结点时,如果该链表仅剩最后一个头结点,不能删除。
其余情况先找到pos结点的前一个结点和后一个结点,将前一个结点的next指针指向后一个结点,后一个结点的prev指针指向前一个结点,然后释放pos结点,并置为空。
//删除pos位置的结点
void ListErase(ListNode* pos)
{assert(pos);//如果该链表仅剩最后一个头结点,不能删除if (pos->next == pos)return;ListNode* prev = pos->prev;ListNode* next = pos->next;prev->next = next;next->prev = prev;free(pos);pos = NULL;
}
实现完该函数后,头删和尾删函数即可复用该函数。
头删即把头结点的next指针指向的结点删除可以直接写成 ListErase(phead->next);
。
尾删即把头结点的prev指针指向的结点删除,可以直接写成ListErase(phead->prev);
。
10.判空函数:
判空函数很简单,如果头结点的next指针指向的结点还是自己即为空,返回true,否则不为空,返回false。
//判空函数
bool ListEmpty(ListNode* phead)
{if (phead->next == phead)return true;return false;
}
11.销毁函数:
销毁函数比较简单,找到第一个结点之后,进入循环,找到下一个结点,然后释放当前结点,直到结点==头结点退出循环,最后释放头结点,并置为空。
//销毁函数
void ListDestory(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){ListNode* tmp = cur;cur = cur->next;free(tmp);tmp = NULL;}free(phead);phead = NULL;
}
四.总结
带头双向循环链表可能结构稍微复杂,但是优点是在任意位置插入删除结点的时间复杂度都是O(1), 缺点是以结点为存储单位,不支持随机访问。
以下是全部源码实现:
1.头文件(声明链表的结构,操作等,起到目录作用):
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>typedef int LTDataType;
typedef struct ListNode
{LTDataType data;//存放的数据元素struct ListNode* prev;//前驱指针struct ListNode* next;//后继指针
}ListNode;
//C++中双向链表叫List,所以起名也叫List//申请新结点
ListNode* BuyListNode(LTDataType x);//打印函数(方便调试观看)
void PrintList(ListNode* phead);//初始化函数
ListNode* ListInit();
//或者:void ListInit(ListNode** pphead);//销毁函数(通过头结点可以链表第一个有效节点进行修改,因此不需要传二级指针)
void ListDestory(ListNode* phead);//尾插函数
void ListPushBack(ListNode* phead, LTDataType x);//头插函数
void ListPushFront(ListNode* phead, LTDataType x);//头删函数
void ListPopFront(ListNode* phead);//尾删函数
void ListPopBack(ListNode* phead);//查找函数
ListNode* ListFind(ListNode* phead, LTDataType x);//在pos位置前插入结点
void ListInsert(ListNode* pos, LTDataType x);//删除pos位置的结点
void ListErase(ListNode* pos);//判空函数
bool ListEmpty(ListNode* phead);
2.源文件(具体实现各种操作):
#include"List.h"//申请新结点
ListNode* BuyListNode(LTDataType x)
{ListNode* NewNode = (ListNode*)malloc(sizeof(ListNode));NewNode->data = x;NewNode->prev = NULL;NewNode->next = NULL;return NewNode;
}//打印函数(方便调试观看)
void PrintList(ListNode* phead)
{ListNode* cur = phead->next;while (cur != phead){printf("%d ", cur->data);cur = cur->next;}printf("NULL\n");
}//初始化函数
ListNode* ListInit()
{//初始链表为空时,需将头结点的prev指针和next指针都指向自己ListNode* phead = BuyListNode(-1);phead->next = phead->prev = phead;return phead;
}
//或:
/*void ListInit(ListNode** pphead)
{//初始链表为空时,需将头结点的prev指针和next指针都指向自己*pphead = BuyListNode(-1);(*pphead)->next = (*pphead)->prev = *pphead;
}*///销毁函数
void ListDestory(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){ListNode* tmp = cur;cur = cur->next;free(tmp);tmp = NULL;}free(phead);phead = NULL;
}//尾插函数
void ListPushBack(ListNode* phead, LTDataType x)
{assert(phead); //ListNode* newnode = BuyListNode(x);//ListNode* tail = phead->prev;//找到最后一个结点//newnode->prev = tail;//新结点的prev指向最后一个结点//newnode->next = phead;//新结点的next指向头结点//tail->next = newnode;//最后一个结点的next指向新结点//phead->prev = newnode;//头结点的prev指向新结点//或者ListInsert(phead, x);
}//头插函数
void ListPushFront(ListNode* phead, LTDataType x)
{assert(phead);//ListNode* newnode = BuyListNode(x);//ListNode* first = phead->next;//找到第一个结点//newnode->next = first;//newnode->prev = phead;//first->prev = newnode;//phead->next = newnode;//或者ListInsert(phead->next, x);
}//头删函数
void ListPopFront(ListNode* phead)
{assert(phead);链表为空时不能删除(或者使用断言)//if (phead->next == phead)// return;//else//{// ListNode* first = phead->next;//找到第一个结点// ListNode* second = first->next;//找到第二个结点// phead->next = second;// second->prev = phead;// free(first);// first = NULL;//}//或者ListErase(phead->next);
}//尾删函数
void ListPopBack(ListNode* phead)
{assert(phead);链表为空时不能删除(或者使用断言)//if (phead->next == phead)// return;//else//{// ListNode* tail = phead->prev;//找到最后一个结点// ListNode* prev = tail->prev;//找到倒数第二个结点// phead->prev = prev;// prev->next = phead;// free(tail);// tail = NULL;//}//或者ListErase(phead->prev);
}//查找函数
ListNode* ListFind(ListNode* phead, LTDataType x)
{assert(phead);//链表为空时,返回NULLif (phead->next == phead)return NULL;else{ListNode* cur = phead->next;while (cur != phead){if (cur->data == x)return cur;elsecur = cur->next;}return NULL;}
}//在pos位置前插入结点
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* newnode = BuyListNode(x);ListNode* prev = pos->prev;//找到前一个结点prev->next = newnode;pos->prev = newnode;newnode->prev = prev;newnode->next = pos;
}//删除pos位置的结点
void ListErase(ListNode* pos)
{assert(pos);//如果该链表仅剩最后一个头结点,不能删除if (pos->next == pos)return;ListNode* prev = pos->prev;ListNode* next = pos->next;prev->next = next;next->prev = prev;free(pos);pos = NULL;
}//判空函数
bool ListEmpty(ListNode* phead)
{if (phead->next == phead)return true;return false;
}
3.测试文件(对各个函数功能进行测试):
#include"List.h"//测试初始化函数
void test01()
{ListNode* phead = ListInit();PrintList(phead);ListDestory(phead);
}//测试尾插函数
void test02()
{ListNode* phead = ListInit();ListPushBack(phead, 1);ListPushBack(phead, 2);ListPushBack(phead, 3);ListPushBack(phead, 4);PrintList(phead);ListDestory(phead);
}//测试头插函数
void test03()
{ListNode* phead = ListInit();ListPushFront(phead, 1);ListPushFront(phead, 2);ListPushFront(phead, 3);ListPushFront(phead, 4);PrintList(phead);ListDestory(phead);
}//测试头删函数
void test04()
{ListNode* phead = ListInit();ListPushBack(phead, 1);ListPushBack(phead, 2);ListPushBack(phead, 3);ListPushBack(phead, 4);PrintList(phead);ListPopFront(phead);PrintList(phead);ListPopFront(phead);PrintList(phead);ListPopFront(phead);PrintList(phead);ListPopFront(phead);PrintList(phead);ListPopFront(phead);PrintList(phead);ListDestory(phead);
}//测试尾删函数
void test05()
{ListNode* phead = ListInit();ListPushBack(phead, 1);ListPushBack(phead, 2);ListPushBack(phead, 3);ListPushBack(phead, 4);PrintList(phead);ListPopBack(phead);PrintList(phead);ListPopBack(phead);PrintList(phead);ListPopBack(phead);PrintList(phead);ListPopBack(phead);PrintList(phead);ListPopBack(phead);PrintList(phead);ListDestory(phead);
}//测试查找函数
void test06()
{ListNode* phead = ListInit();ListPushBack(phead, 1);ListPushBack(phead, 2);ListPushBack(phead, 3);ListPushBack(phead, 4);PrintList(phead);//查找是否有值为1的结点if (ListFind(phead,1) != NULL){printf("存在值为1的结点\n");}else{printf("不存在值为1的结点\n");}//查找是否有值为57的结点if (ListFind(phead, 57) != NULL){printf("存在值为57的结点\n");}else{printf("不存在值为57的结点\n");}ListDestory(phead);
}//测试在结点前插入函数
void test07()
{ListNode* phead = ListInit();ListPushBack(phead, 1);ListPushBack(phead, 2);ListPushBack(phead, 3);ListPushBack(phead, 4);PrintList(phead);//在第三个结点前插入300(首先找到第三个结点)ListNode* pos1 = ListFind(phead, 3);ListInsert(pos1, 300);PrintList(phead);//在第1个结点前插入100(首先找到第1个结点)ListNode* pos2 = ListFind(phead, 1);ListInsert(pos2, 100);PrintList(phead);//在最后一个结点前插入400(首先找到最后一个结点)ListNode* pos3 = ListFind(phead, 4);ListInsert(pos3, 400);PrintList(phead); ListDestory(phead);
}//测试删除pos结点
void test08()
{ListNode* phead = ListInit();ListPushBack(phead, 1);ListPushBack(phead, 2);ListPushBack(phead, 3);ListPushBack(phead, 4);PrintList(phead);删除值为1的结点(首先先找到值为1的结点)ListNode* pos1 = ListFind(phead, 1);ListErase(pos1);PrintList(phead);删除值为3的结点(首先先找到值为3的结点)ListNode* pos2 = ListFind(phead, 3);ListErase(pos2);PrintList(phead);//删除值为4的结点(首先先找到值为4的结点)ListNode* pos3 = ListFind(phead, 4);ListErase(pos3);PrintList(phead);ListDestory(phead);
}//测试判空函数
void test09()
{ListNode* phead = ListInit();ListPushBack(phead, 1);ListPushBack(phead, 2);ListPushBack(phead, 3);ListPushBack(phead, 4);PrintList(phead);if (ListEmpty(phead))printf("链表为空\n");elseprintf("链表不为空\n");ListNode* phead2 = ListInit();if (ListEmpty(phead2))printf("链表为空\n");elseprintf("链表不为空\n");ListDestory(phead);ListDestory(phead2);
}int main()
{//test01();test02();//test03();//test04();//test05();//test06();//test07();//test08();//test09();return 0;
}
感谢阅读