前言
1.概念
单向链表:一块内存指向下一个内存。 单链表存在一些缺陷: 1.查找速度慢。 2.不能从后往前找。 3.找不到前驱。 链表的结构分为8种: 1.单向和双向 2.带头和不带头 带头的链表有一个带哨兵位的头结点,这个节点不存储有效数据。 好处:尾插更方便,不需要二级指针了,带头结点不需要改变传过来的指针,,也就意味着不需要传二级指针 3.循环和不循环 不循环:尾是指向空的 循环:尾是指针头的 一、无头单向肺循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的领接表等等。 另外这种结构在笔试面试中出现很多。 二、带头双向循环链表:结构复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表,另外这个结构虽然结构复杂, 但是使用代码实现以后会发现带来很多优势,实现反而简单了。
2.初始化
代码语言:javascript复制typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}ListNode;
next表示指向下一个节点 prev表示前驱指针,指向上一个节点
一、双向链表的应用
1.打印链表和创建新的节点
代码语言:javascript复制//创建新的节点
ListNode* BuyListNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
//打印链表
void ListPrint(ListNode* phead)
{
ListNode* cur = phead->next;
while (cur!= phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("n");
}
2.链表的初始化和销毁
代码语言:javascript复制//初始化
ListNode* ListInit()
{
ListNode* phead = BuyListNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
//销毁
void ListDestory(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}
3.链表的首插和尾插
代码语言:javascript复制//尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* tail = phead->prev;
ListNode* newnode = BuyListNode(x);
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
//上面的尾插针对空链表也是成立的,可以画个图理解一下
//双向带头循环链表结构虽然复杂了,但是操作反而简单了。
//结构设计的优势
//内存占用增加,每个节点多了一个前驱指针
//STL中的List原码就是这个结构
//头插
void ListPushfront(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* newnode = BuyListNode(x);
ListNode* first = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = first;
first->prev = newnode;
}
4.链表的尾删和头删
代码语言:javascript复制//尾删
void ListPopBack(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListNode* tail = phead->prev;
ListNode* last = tail->prev;
free(tail);
last->next = phead;
phead->prev = last;
tail = NULL;
}
//头删
void ListPopFront(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListNode* first = phead->next;
ListNode* second = first->next;
free(first);
phead->next = second;
second->prev = phead;
first = NULL;
}
5.链表的查找和指定位置的插入删除
代码语言:javascript复制//查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* cur = phead->next;
while (cur!=phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//插入
void ListInsert(ListNode* phead, ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* last = pos->prev;
ListNode* newnode = BuyListNode(x);
last->next = newnode;
newnode->prev = last;
newnode->next = pos;
pos->prev = newnode;
}
//删除pos位置的数
void ListErase(ListNode* plist, ListNode* pos)
{
assert(pos);
ListNode* last = pos->prev;
ListNode* following = pos->next;
free(pos);
last->next = following;
following->prev = last;
pos = NULL;
}
二、封装文件
注:以上函数封装在List.c中
1. List.h文件
代码语言:javascript复制#pragma once
//双向链表定义
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}ListNode;
ListNode* ListInit();
void ListPrint(ListNode* plist);
void ListDestory(ListNode* plist);
void ListPushBack(ListNode* plist, LTDataType x);
void ListPushfront(ListNode* plist, LTDataType x);
void ListPopBack(ListNode* plist);
void ListPopFront(ListNode* plist);
ListNode* ListFind(ListNode* plist, LTDataType x);
//在pos位置之前插入一个值
void ListInsert(ListNode* plist, ListNode* pos, LTDataType x);
//删除pos位置的值
void ListErase(ListNode* plist, ListNode* pos);
2.测试函数
代码语言:javascript复制#include "List.h"
void TestList1()
{
ListNode* plist = ListInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPrint(plist);
ListDestory(plist);
}
void TestList2()
{
ListNode* plist = ListInit();
ListPushfront(plist, 1);
ListPushfront(plist, 2);
ListPushfront(plist, 3);
ListPushfront(plist, 4);
ListPrint(plist);
ListDestory(plist);
}
void TestList3()
{
ListNode* plist = ListInit();
ListPushfront(plist, 1);
ListPushfront(plist, 2);
ListPushfront(plist, 3);
ListPushfront(plist, 4);
ListPrint(plist);
printf("n");
ListPopBack(plist);
ListPrint(plist);
ListDestory(plist);
}
void TestList4()
{
ListNode* plist = ListInit();
ListPushfront(plist, 1);
ListPushfront(plist, 2);
ListPushfront(plist, 3);
ListPushfront(plist, 4);
ListPrint(plist);
printf("n");
ListPopFront(plist);
ListPrint(plist);
ListDestory(plist);
}
void TestList5()
{
ListNode* plist = ListInit();
ListPushfront(plist, 1);
ListPushfront(plist, 2);
ListPushfront(plist, 3);
ListPushfront(plist, 4);
ListPrint(plist);
ListNode* pos = ListFind(plist, 3);
if (pos)
{
//查找同时可以附带修改
printf("找到了");
pos->data = pos->data * 10;
}
else
{
printf("没有找到");
}
}
void TestList6()
{
ListNode* plist = ListInit();
ListPushfront(plist, 1);
ListPushfront(plist, 2);
ListPushfront(plist, 3);
ListPushfront(plist, 4);
ListPrint(plist);
printf("n");
ListNode* pos = ListFind(plist, 3);
if (pos)
{
ListInsert(plist, pos, 100);
ListErase(plist, pos->prev);
}
ListPrint(plist);
}
int main()
{
TestList6();
return 0;
}
用以上函数进行测试
总结
虽然双向链表结构付咱,但是理解其结构可发现各个接口比单向链表的接口写起来更加简单,更加容易上手