当前位置: 首页 > news >正文

淮北集团网站建设/网店seo

淮北集团网站建设,网店seo,游戏网站规划方案,gif网站素材【数据结构】结构最复杂实现最简单的双向带头循环链表 一、前言二、目标三、实现1、初始化工作2、尾插2.1、图解思路2.2、代码实现 3、尾删3.1、图解思路3.2、代码实现 4、打印链表5、头插5.1、图解思路5.2、代码实现 6、头删6.1、图解思路6.2、代码实现 7、查找8、随机插入8.1…

【数据结构】结构最复杂实现最简单的双向带头循环链表

  • 一、前言
  • 二、目标
  • 三、实现
    • 1、初始化工作
    • 2、尾插
      • 2.1、图解思路
      • 2.2、代码实现
    • 3、尾删
      • 3.1、图解思路
      • 3.2、代码实现
    • 4、打印链表
    • 5、头插
      • 5.1、图解思路
      • 5.2、代码实现
    • 6、头删
      • 6.1、图解思路
      • 6.2、代码实现
    • 7、查找
    • 8、随机插入
      • 8.1、图解思路
      • 8.2、代码实现
    • 9、随机删除
      • 9.1、图解思路
      • 9.2、代码实现
    • 10、链表的销毁

一、前言

有人可能会疑惑为什么突然就从结构最简单的单向无头非循环链表转到结构最复杂度双向带头循环链表,这好像跨度有点大啊。
但这两个链表是两个很好的极端,单向无头非循环链表虽然结构最简单:
在这里插入图片描述
但也正因为其结构最简单给它带来了很多局限性,比如对于每个节点我们只能找到它的后继而不能找到它的前驱,尾插的时候总是需要从头遍历找到尾节,总是要特殊处理头指针的改变等等……
所以单向无头非循环链表实现起来应该是最难的。
而双向带头循环链表虽然结构最复杂:
在这里插入图片描述
但也正是因为它复杂度结构使得它在使用的时候有更多的选择,比如对于每个节点,我们不仅能找到它的后继,也能找到它的前驱,或者是找尾很方便。
所以双向带头循环链表的实现应该是最简单的。
所以只要搞清楚了这两个链表,那我们再看其他结构的链表的时候也就游刃有余了。

二、目标

双向带头循环链表所需要实现的功能大致如下:

// 初始化链表
void InitList(ListNode* head);
// 创建一个新节点,返回节点指针
ListNode* create_newnode(data_type x);
// 双向循环链表的尾插
void double_circle_list_push_back(ListNode* head, data_type x);
// 双向循环链表的尾删
void double_circle_list_pop_back(ListNode *head);
// 双向循环链表的打印
void print_double_circle_list(ListNode* head);
// 双向循环链表的头插
void double_circle_list_push_front(ListNode* head, data_type x);
// 双向循环链表的头删
void double_circle_list_pop_front(ListNode* head);
// 双向循环链表的查找,返回查找到的节点的指针,若找不到则返回NULL
ListNode* find_Node(ListNode* head, data_type x);
// 双向循环链表的随机插入,在目标节点的后面插入新节点
void double_circle_list_insert(ListNode* target, data_type x);
// 双向循环链表的随机删除,从链表中删除目标节点
void double_circle_list_remove(ListNode* target);
// 双向循环链表的销毁
void destroy_double_circle_list(ListNode* head);

其实这跟单链表的功能也大差不差,只是实现的思路不一样而已。
那接下来就让我们一个一个的实现吧。

三、实现

1、初始化工作

同样的我们还是先把节点类型定义了和数据域的数据类型重定义了:

// 将数据类型重定义
typedef int data_type;// 创建节点类型
typedef struct double_circle_list_node {data_type val;struct double_circle_list_node* prev;struct double_circle_list_node* next;
} ListNode ;

接下来我们就可以对已有的链表头节点进行初始化了,循环链表必须保证在任何情况下都成环,所以我们初始化时,应该把头节点的next和prev都指向它自己:
在这里插入图片描述
代码如下:

// 初始化链表
void InitList(ListNode* head) {assert(head);head->prev = head;head->next = head;
}

而又因为我们后面在写个各个插入节点的函数的时候需要动态开辟一个新的节点,所以我们就行一个函数用来创建一个新节点,返回节点指针:

// 创建一个新节点,返回节点指针
ListNode* create_newnode(data_type x) {ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));if (NULL == newNode) {perror("malloc fail\n");exit(-1);}newNode->val = x;return newNode;
}

这样我们的初始化工作就都完成了。

2、尾插

2.1、图解思路

比起单链表,双向循环链表找起尾来简直不要太简单,因为头节点的prev始终都指向链表的最后一个节点,所以尾节点tail即为head->prev:
在这里插入图片描述
所以我们创建好新的节点后就可以直接将各个指针指向相应的地方了:
在这里插入图片描述
分别是:

newNode->prev = tail;
tail->next = newNode;
newNode->next = head;
head->prev = newNode;

当然这四个指针的更改顺序随便打乱也是毫无影响的。
而我们是否需要额外考虑链表中只有一个头节点的情况呢?
其实是不用的,因为如果链表中只有一个头结点,那么按照上面的逻辑,tail指向的也还是头节点:
在这里插入图片描述
这时候我们再执行上面一样的逻辑,也还是能达到效果的:
在这里插入图片描述

2.2、代码实现

// 双向循环链表的尾插
void double_circle_list_push_back(ListNode* head, data_type x) {assert(head);// 先创建新节点ListNode* newNode = create_newnode(x);ListNode* tail = head->prev;// 插入newNode->prev = tail;tail->next = newNode;newNode->next = head;head->prev = newNode;
}

3、尾删

3.1、图解思路

因为是双向链表,所以我们就可以直接找到tail的前一个节点,所以我们的尾删当然也很简单啦:
在这里插入图片描述
如图,我们可以想让两个指针tail和tailPre分别指向尾节点和尾节点的前一个节点,然后我们可以先释放表tail再修改相应的指针或先修改相应的指针再释放都行,这两个操作的先后顺序不影响结果:
在这里插入图片描述
当然我们这里也并不需要考虑链表中只剩一个有小姐点的情况,对于双向循环链表来说这些都是一步到位的。
当然了,我们传入的head是不能为空的,并且也要保证链表中至少有一个有效节点,不能只有一个头节点head,因为不能把head也给删。

3.2、代码实现

// 双向循环链表的尾删
void double_circle_list_pop_back(ListNode *head) {assert(head);assert(head->next != head);ListNode* tail = head->prev;ListNode* tailpre = tail->prev;free(tail);tailpre->next = head;head->prev = tailpre;
}

4、打印链表

打印其实是和单向链表是一样的,我们从前往后遍历链表中的节点然后又打印出节点信息即可,只不过头节点是不用打印的,所以我们可以直接从head的next开始打印。
而因为循环链表是没有空节点的,所以我们循环结束的条件就不是为空了,但我们可以判断时候回到了头节点head,因为头节点是不用打印的:

// 双向循环链表的打印
void print_double_circle_list(ListNode* head) {assert(head);if (head->next == head) {printf("NULL\n");return;}ListNode* cur = head->next;printf("[head]∞");while (cur != head) {printf("[%d]∞", cur->val);cur = cur->next;}printf("\n");
}

打印效果如下:
在这里插入图片描述

5、头插

5.1、图解思路

头插的操作几乎和单链表的相同,只不过多出了个prev指针需要我们处理而已:
在这里插入图片描述

如上如,我们可以先用一个first指针指向第一个节点,然后各个指针的修改顺序就可以随意打乱也不会影响结果了。
当然,只有一个头节点的情况也是不需要特殊考虑的,直接用相同的操作即可。

5.2、代码实现

// 双向循环链表的头插
void double_circle_list_push_front(ListNode* head, data_type x) {assert(head);ListNode* newNode = create_newnode(x);ListNode* first = head->next;newNode->next = first;first->prev = newNode;head->next = newNode;newNode->prev = head;
}

6、头删

6.1、图解思路

头删其实也是和单链表只差了一个prev指针:
在这里插入图片描述
我们可以先将新的第一个节点(第二个节点)用一个newfirst指针先存起来,然后在修改相应的指针指向。

6.2、代码实现

// 双向循环链表的头删
void double_circle_list_pop_front(ListNode* head) {assert(head );assert(head->next != head);ListNode* newfirst = head->next->next; // 新的第一个节点free(head->next);head->next = newfirst;newfirst->prev = head;
}

7、查找

查找那就和单向链表一模一样了,就像打印链表一样从前往后遍历节点,找到只相同的节点直接返回即可:

// 双向循环链表的查找,根据数据域的值查找,返回查找到的节点的指针,若找不到则返回NULL
ListNode* find_Node(ListNode* head, data_type x) {assert(head && head->next != head);ListNode* cur = head->next;while (cur != head) {if (cur->val == x) {return cur;}cur = cur->next;}return NULL;
}

8、随机插入

8.1、图解思路

通过传入目标节点的指针,实现在目标节点的后面插入一个新节点。
这个函数需要配合着上面的查找函数一起使用,先用查找函数找到目标节点,再将目标节点传入插入函数就可以实现随机插入了。
置于插入的思路就不用多说了,基本和尾插头插是一个逻辑。

8.2、代码实现

// 双向循环链表的随机插入,在目标节点的后面插入新节点
void double_circle_list_insert(ListNode* target, data_type x) {assert(target);ListNode* newNode = create_newnode(x);ListNode* next = target->next;target->next = newNode;newNode->prev = target;newNode->next = next;next->prev = newNode;
}

9、随机删除

9.1、图解思路

通过传入的目标节点,将目标节点从链表中删除。
获取单链表还需要从前往后遍历找到目标节点的前一个节点,但双向链表就不用。
我们直接执行target->prev->next = target->next和target->next->prev = target->prev,然后将在释放掉target即可:
在这里插入图片描述
当然了,直接这样操作的话可能会有的朋友会晕,如果觉得晕的话可以多定义两个指针,这样就容易理解一点。

9.2、代码实现

// 双向循环链表的随机删除,从链表中删除目标节点
void double_circle_list_remove(ListNode* target) {assert(target);target->prev->next = target->next;target->next->prev = target->prev;free(target);
}

10、链表的销毁

销毁也是和单链表的一样的:

// 双向循环链表的销毁
void destroy_double_circle_list(ListNode* head) {assert(head && head->next != head);ListNode* cur = head->next;ListNode* next = cur->next;while (cur != head) {next = cur->next;free(cur);cur = next;}
}

这里并没有传入二级指针来达到在函数内销毁头节点,但我们在使用时需要记得最后要连头节点head也销毁了。

http://www.jmfq.cn/news/5005927.html

相关文章:

  • wordpress安装不成功/长沙优化排名推广
  • 如何做英文网站/深圳网站建设专业乐云seo
  • 龙岗网站制作培训班/重庆百度推广开户
  • 国家家企业信用信息系统/重庆百度seo代理
  • 网站建设中主机放在哪里/专业做网站公司
  • 北京做网站公司/教育培训平台
  • 肖鸿昌建筑网站/市场监督管理局电话
  • 常德网站建设套餐报价/制作网页用什么软件
  • 长沙好的网站建设公司排名/网络营销成功的原因
  • 全网vip影视自助建站系统/河南新站关键词排名优化外包
  • 免费网站建站 网页/宁波网络推广产品服务
  • 做网站有哪些/关键词怎么做快速的有排名
  • wordpress视频插入/关键词seo
  • 网站做软件有哪些/营销方案ppt
  • 医疗图片做网站图片/网络营销的发展现状如何
  • 做网站怎么套模板/永久开源的免费建站系统
  • 北京黄村专业网站建设价钱/网站seo 工具
  • 建设银行怎么加入信用网站/淘宝指数转换
  • 网站的用户体验主要有那些类型/常用seo站长工具
  • 做旅游网站平台ppt/淘宝运营培训多少钱
  • 公司宣传网站怎么做/营销咨询公司经营范围
  • 专门做别墅的网站/门户网站
  • 校园旅游网站建设方案策划书/小红书如何引流推广
  • 个人做网站seo/热门关键词查询
  • 视频制作方法/免费检测网站seo
  • 建设云南省癌症中心网站/做一套二级域名网站怎么做
  • 采购公告 校园网站建设/磁力链
  • b2b平台的优势与劣势/广西网站seo
  • 网站方案制作的培训/百度云盘下载
  • 财经类 直播类网站开发/免费培训网站