系列文章目录
数据结构——顺序表的C语言代码实现
数据结构——八种链表的C语言代码实现
数据结构——栈的C语言代码实现
文章目录
前言
众所周知,链表主要有八种形式:
在上篇文章中我们实现了形式上最为简便的单项不带头非循环链表,了解了链表的基本特点和实现方法,故在此基础上来实现更为使用的双向带头循环链表。
提示:以下是本篇文章正文内容,下面案例可供参考
一、基础知识
1.双向链表的概念
双向链表较与单链表最大的区别便是:多了一个指向前一个节点的指针。
在实现双向带头循环链表前,我们需要先弄明白何为头指针,头结点,首元节点。
通俗的理解中:
·头指针便是链表中第一个节点的存储地址;
·头结点是一个不存储数据的节点,它的作用是使得链表的头指针非空,便于插入和删除等操作的实现;
·首元节点是链表中用于存储线性表第一个数据元素的节点。
2.头结点的优势
头结点优势在于使头指针永远非空。
这样就使得:
· 尾插中不用讨论头指针为空的情况,也不用进行传址调用;
·尾删中不会出现改变头指针的情况,只需要传值调用
·等等…
二、代码实现
1.List.h
(1)引用函数库
代码如下(示例):
尤其注意引用stdlib.h函数库,如果忘记引用该函数库,而直接使用malloc等函数时,编译器不会报错或警告,但程序总是崩溃或不执行,这会使得我们不断怀疑程序编写的正确性,浪费大量时间查错。
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
(2)定义链表
代码如下(示例):
//便于统一更改所存储数据的类型
typedef int ListDataType;
//定义双向链表的节点
typedef struct ListNode
{
ListDataType data;
struct ListNode* next;
struct ListNode* prev;
}LN;
(3)接口函数声明
代码如下(示例):
注意凡是带有头结点的链表,一般都有初始化头结点这一接口函数,实现头指针的永久非空!
//初始化头结点
void ListInit(LN** phead);
//尾插法
void ListPushBack(LN* phead,ListDataType x);
//头插法
void ListPopBack(LN* phead);
//尾删法
void ListPushFront(LN* phead, ListDataType x);
//头删法
void ListPopFront(LN* phead);
//查找链表中某数值所属节点的位置
LN* ListFind(LN* phead, ListDataType x);
//在指定节点前插入
void ListInsertFront(LN* phead, ListDataType x,ListDataType insert);
//在指定节点后插入
void ListInsertBack(LN* phead, ListDataType x, ListDataType insert);
//更改指定节点
void ListNodeChange(LN* phead, ListDataType x, ListDataType num);
//删除指定节点
void ListDeleteNode(LN* phead, ListDataType x);
//删除链表
void ListFree(LN* phead);
//打印链表
void ListPrint(LN* phead);
2.List.c
实现各个接口函数,注意引用List.h头文件。
#include"List.h"
(1)初始化头结点
代码如下(示例):
//初始化头结点
void ListInit(LN** phead)
{
//带头节点的链表的最大特点便在于初始化头结点
//使得头指针在插入前便指向头结点
//省去了插入,删除过程中判断是否改变头指针的工作
*phead = (LN*)malloc(sizeof(LN));
(* phead)->next = *phead;
(*phead)->prev = *phead;
}
(2)尾插法
代码如下(示例):
//尾插法
void ListPushBack(LN* phead,ListDataType x)
{
//无需传入二级指针,因为头指针始终指向头结点
//插入过程中不会改变头指针,故只需传值调用
if (phead == NULL)
{
printf("未对头结点进行初始化!\n");
return;
}
LN* tail = phead->prev;
LN* tem = (LN*)malloc(sizeof(LN));
tem->data = x;
tail->next = tem;
tem->prev = tail;
tem->next = phead;
phead->prev = tem;
}
(3)尾删法
代码如下(示例):
//尾删法
void ListPopBack(LN* phead)
{
if (phead->next == NULL)
{
printf("链表为空\n");
return;
}
LN* tail = phead->prev;
tail->prev->next = phead;
phead->prev = tail->prev;
free(tail);
}
(4)头插法
代码如下(示例):
//头插法
void ListPushFront(LN* phead, ListDataType x)
{
if (phead == NULL)
{
printf("未对头结点进行初始化!\n");
return;
}
LN* tem = (LN*)malloc(sizeof(LN));
tem->data = x;
phead->next->prev = tem;
tem->next = phead->next;
phead->next = tem;
tem->prev = phead;
}
(5)头删法
代码如下(示例):
//头删法
void ListPopFront(LN* phead)
{
if (phead->next == NULL)
{
printf("链表为空\n");
return;
}
LN* tem = phead->next->next;
free(phead->next);
phead->next = tem;
tem->prev = phead;
}
(6)查找链表中某数值所属节点的位置
代码如下(示例):
//查找链表中某数值所属节点的位置
LN* ListFind(LN* phead, ListDataType x)
{
if (phead->next == NULL)
{
printf("链表为空\n");
return;
}
LN* tem = phead->next;
while (tem != phead)
{
if (tem->data == x)
{
return tem;
}
tem = tem->next;
}
return NULL;
}
(7)在指定节点前插入
代码如下(示例):
//在指定节点前插入
void ListInsertFront(LN* phead, ListDataType x,ListDataType insert)
{
LN* tem = ListFind(phead, x);
if (tem == NULL)
{
printf("链表中没有%d\n",x);
return;
}
else
{
LN* pos = tem->prev;
pos->data = insert;
}
}
(8)在指定节点后插入
代码如下(示例):
//在指定节点后插入
void ListInsertBack(LN* phead, ListDataType x, ListDataType insert)
{
LN* tem = ListFind(phead, x);
if (tem == NULL)
{
printf("链表中没有%d\n", x);
return;
}
else
{
LN* pos = tem->next;
pos->data = insert;
}
}
(9)删除指定节点
代码如下(示例):
//删除指定节点
void ListDeleteNode(LN* phead, ListDataType x)
{
LN* tem = ListFind(phead, x);
if (tem == NULL)
{
printf("链表中没有%d\n", x);
return;
}
else
{
LN* prevnode = tem->prev;
LN* nextnode = tem->next;
prevnode->next = nextnode;
nextnode->prev = prevnode;
free(tem);
}
}
(10)改变指定节点
代码如下(示例):
//改变指定节点
void ListNodeChange(LN* phead, ListDataType x, ListDataType num)
{
LN* tem = ListFind(phead, x);
if (tem == NULL)
{
printf("链表中没有%d\n", x);
return;
}
else
{
tem->data = num;
}
}
(11)删除链表
代码如下(示例):
//删除链表
void ListFree(LN* phead)
{
LN* tem = phead->next;
while (tem != phead)
{
LN* nextnode = tem->next;
free(tem);
tem = nextnode;
}
free(phead);
}
(12)打印链表
代码如下(示例):
//打印链表
void ListPrint(LN* phead)
{
LN* tem = phead->next;
while (tem!=phead)
{
printf("%2d ", tem->data);
tem = tem->next;
}
printf("\n");
}
3.test.c
代码如下(示例):
#include"List.h"
int main()
{
LN* plist = NULL;
ListInit(&plist);
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 5);
ListPushFront(plist, 6);
ListPushFront(plist, 7);
ListPushFront(plist, 8);
ListPushFront(plist, 9);
ListPrint(plist);
ListPopBack(plist);
ListPrint(plist);
ListPopFront(plist);
ListPrint(plist);
ListInsertFront(plist, 6, 11);
ListPrint(plist);
ListInsertBack(plist, 11, 12);
ListPrint(plist);
ListFree(plist);
ListDeleteNode(plist, 1);
ListDeleteNode(plist, 8);
ListDeleteNode(plist, 4);
ListPrint(plist);
ListFree(plist);
return 0;
}
总结
本文在上篇单链表的基础上延伸实现了双向带头循环链表,相信各位也在实现该链表的过程中,发现了头结点和循环的妙处。虽然看似最复杂,但是代码实现却是最为逻辑的,简便的!
如果读完有所脾益,就麻烦大家动手点个赞和关注,感谢!
坚持手敲数据结构,我们下篇见!