假设存储在链表中的数据是int类型的,重命名为LTDataType
typedef int SLDataType;
定义一个结构体,每个结构体是一个结点,在这个结点中包括指向上一个节点的指针,指向下一个结点的指针以及有效数据,将结构体重命名为List
typedef struct List
{
struct List* prev;
struct List* next;
SLDataType data;
}List;
在头文件中声明要实现功能的函数
//声明函数
//初始化双向链表
List* InitList();
//销毁双向链表
void DestoryList(List* phead);
//创建结点
List* CreateNode(SLDataType x);
//打印双向链表
void PrintList(List* phead);
//头插
void ListPushFront(List* phead, SLDataType x);
//尾插
void ListPushBack(List* phead, SLDataType x);
//头删
void ListPopFront(List* phead);
//尾删
void ListPopBack(List* phead);
//查找结点
List* ListFind(List* phead, SLDataType x);
//插入中间
void ListInsert(List* pos, SLDataType x);
//删除中间结点
void ListErase(List* pos);
//计算有效结点数目
int ListSize(List* phead);
//判断双向链表是否为空
bool ListEmpty(List* phead);
1.创建新结点
//创建结点
List* CreateNode(SLDataType x)
{
List* newnode = (List*)malloc(sizeof(List));
//初始化结点
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
创建完结点后初始化它,将x赋给它的data,因为返回值是结点的指针,因此在这里不需要传二级指针,直接将创建的结点返回即可。
2.初始化双向链表
//初始化双向链表
List* InitList()
{
List* phead = CreateNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
初始化双向链表,因为是带头的,所以先创建头结点,调用CreateNode函数,头结点的prev和next都指向自己,初始化就完成了。
3.销毁双向链表
//销毁双向链表
void DestoryList(List* phead)
{
assert(phead);
List* cur = phead->next;
while (cur != phead)
{
List* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}
要销毁双向链表,需要将每个结点都free掉,最后还要将哨兵位的结点free掉,并置为空,在free的过程中要创建next记住下一个结点,因为free掉当前节点后就找不到下一个结点了,因此要记住它
4.打印双向链表有效数据
//打印双向链表
void PrintList(List* phead)
{
assert(phead);
List* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
这个没什么好说的,结束条件是当cur等于phead
5.头插
//头插
void ListPushFront(List* phead, SLDataType x)
{
assert(phead);
List* first = phead->next;
List* newnode = CreateNode(x);
phead->next = newnode;
newnode->prev = phead;
newnode->next = first;
first->prev = newnode;
//ListInsert(phead->next, x);
}
双向循环链表的一个很大的好处就是他不会指向空,所以不论除了头结点外你是否还有结点,他都可以连起来
6.尾插
//尾插
void ListPushBack(List* phead, SLDataType x)
{
assert(phead);
List* tail = phead->prev;
List* newnode = CreateNode(x);
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
//ListInsert(phead, x);
}
跟头插一样,除了头结点外,不管你有没有结点,他都能连起来。没有结点的话就跟头插是一样的
7.头删
//头删
void ListPopFront(List* phead)
{
assert(phead);
assert(phead->next != phead);
List* first = phead->next;
List* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
first = NULL;
//ListErase(phead->next);
}
在这里要断言必须起码有一个有效节点,将第一个结点定义为first,第二个节点定义为second,将phead和second连起来,free掉first就行了
8.尾删
//尾删
void ListPopBack(List* phead)
{
assert(phead);
assert(phead->next != NULL);
List* tail = phead->prev;
List* prev = tail->prev;
prev->next = phead;
phead->prev = prev;
free(tail);
tail = NULL;
//ListErase(phead->prev);
}
跟头删一样,也要断言必须起码有一个结点,将最后一个节点定义为tail,tail的前一个结点定义为prev,将phead和prev连起来,再将tail给free掉就行了,如果只有一个结点的话也是成立的
9.查找结点
//查找结点
List* ListFind(List* phead, SLDataType x)
{
assert(phead);
List* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
遍历链表,从phead的next开始,如果找到了就返回那个结点的地址,找不到就返回空指针,结束条件是当cur==phead
10.插入中间
//插入中间
void ListInsert(List* pos, SLDataType x)
{
assert(pos);
List* prev = pos->prev;
List* newnode = CreateNode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
通过ListFind函数找到要插入的结点,pos前面的结点定义为prev,将prev和newnode连起来,再将newnode和pos连起来jiuxing
11.删除中间结点
//删除中间结点
void ListErase(List* pos)
{
assert(pos);
List* prev = pos->prev;
List* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
pos = NULL;
}
通过ListFind函数找到要删除的结点pos,pos前的结点定义为prev,后一个结点定义为next,prev和next连起来,再将pos给free掉即可
12.计算有效结点数目
//计算有效结点数目
int ListSize(List* phead)
{
assert(phead);
List* cur = phead->next;
int count = 0;
while (cur != phead)
{
count++;
cur = cur->next;
}
return count;
}
定义count变量用来记录,遍历链表,在返回count
13.判断链表是否为空
//判断双向链表是否为空
bool ListEmpty(List* phead)
{
assert(phead);
return phead->next == phead;
}
如果头结点的next还是头结点的话,就表示除了头结点,没有其他结点,也即是说链表是空,返回true,反之就返回false
注意:在头插尾插头删尾删中分别可以调用ListInsert、ListErase函数,头插就将phead的next地址传给ListInsert函数,尾插将phead传给ListInsert,头删将phead的next传给ListErase,尾删就将phead的prev传给ListErase,效果是一样的,在将来面试如果面试官让你在短时间内写出增删查改等基本操作,可以使用这样的方法节省时间。
下面是一些测试用例
void test1()
{
List* head = InitList();
ListPushBack(head, 1);
ListPushBack(head, 2);
ListPushBack(head, 3);
ListPushBack(head, 4);
PrintList(head);
ListPushFront(head, 0);
ListPushFront(head, -1);
PrintList(head);
ListPopFront(head);
PrintList(head);
ListPopBack(head);
ListPopBack(head);
PrintList(head);
List* pos = ListFind(head, 2);
if (pos)
{
ListInsert(pos, 20);
}
PrintList(head);
ListErase(pos);
PrintList(head);
printf("%d\n", ListSize(head));
DestoryList(head);
}
void test2()
{
List* head = InitList();
ListPushBack(head, 1);
ListPushBack(head, 2);
ListPushBack(head, 3);
ListPushBack(head, 4);
PrintList(head);
ListPushFront(head, 0);
ListPushFront(head, -1);
PrintList(head);
List* pos = ListFind(head, 3);
if (pos)
{
ListInsert(pos, 30);
}
PrintList(head);
ListErase(pos);
PrintList(head);
printf("%d\n", ListSize(head));
DestoryList(head);
}
int main()
{
test1();
test2();
return 0;
}
将他们对照着理解效果会更好
以上就是本次带头双向链表的一些基本操作,如果对你有用的话就点个赞吧......