【数据结构】双向链表


个人主页点这里~


双向链表

一、双向链表的结构

1、带头双向循环列表

头:指一个固定点,我们叫它哨兵位,它不存储任何数据,只是起到作为一个哨兵的作用,跟头节点的意义不同

双向链表概念图:

每一个节点由一个数据点和两个指针点组成,指针点一指向前面的元素,指针点二指向后边的元素

二、实现双向链表

1、ListNode.h

c 复制代码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next; 
	struct ListNode* prev; 
	LTDataType data;
}LTNode;
//节点创建函数
LTNode* LTBuyNode(LTDataType x);
//初始化
LTNode* LTInit();
//摧毁
void LTDestroy(LTNode* phead);
//打印
void LTPrint(LTNode* phead);
//检查链表是否只有哨兵位
bool LTEmpty(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//头删
void LTPopFront(LTNode* phead);
//pos节点后插入
void LTInsert(LTNode* pos, LTDataType x);
//删除pos节点
void LTErase(LTNode* pos);
//查找某个数据的位置
LTNode* LTFind(LTNode* phead, LTDataType x);

2、ListNode.c

c 复制代码
#include "ListNode.h"

//节点创建函数
LTNode* LTBuyNode(LTDataType x)
{
    LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
    if (newnode == NULL)
    {
        perror("malloc fail");
        exit(1);
    }
    newnode->data = x;
    newnode->prev = newnode->next = newnode;
    return newnode;
}

//初始化,建立哨兵位
LTNode* LTInit()
{
    LTNode* head = LTBuyNode(-1);
    return head;
}

//销毁链表
void LTDestroy(LTNode* phead)
{
    assert(phead);
    LTNode* n1 = phead->next;
    while (n1 != phead)
    {
        LTNode* n2 = n1->next;
        free(n1);
        n1 = n2;
    }//将除了哨兵位以外的所有节点销毁
    free(phead);//最后销毁哨兵位
}

//打印链表
void LTPrint(LTNode* phead)
{
    LTNode* pur = phead->next;
    while (pur != phead)
    {
        printf("%d ", pur->data);
        pur = pur->next;
    }
    printf("\n");
}

//检查链表是否只含有哨兵位
bool LTEmpty(LTNode* phead)
{
    if (phead->next == phead && phead->prev == phead)
        return 1;
    else
        return 0;
}//当只含哨兵位时,phead的next与prev指针都是指向phead的

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
    assert(phead);
    LTNode* newnode = LTBuyNode(x);
    phead->prev->next = newnode;
    newnode->prev = phead->prev;
    newnode->next = phead;
    phead->prev = newnode;//将新创建的newcode节点连接phead以及此时的phead前一个结点
}

//尾删
void LTPopBack(LTNode* phead)
{
    assert(phead && phead->next != phead);
    LTNode* del = phead->prev;
    del->prev->next = phead;
    phead->prev = del->prev;
    free(del);
}

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
    LTNode* pur = phead->next;
    LTNode* newnode = LTBuyNode(x);
    phead->next = newnode;
    newnode->prev = phead;
    newnode->next = pur;
    pur->prev = newnode;
}//头插的意义是在哨兵位和第一个有效节点之间插入节点

//头删
void LTPopFront(LTNode* phead)
{
    LTNode* pur = phead->next;
    LTNode* purr = pur->next;
    phead->next = purr;
    purr->prev = phead;
    free(pur);
}//头删的意义是删除哨兵位后边的这一个节点

//在指定位置后插
void LTInsert(LTNode* pos, LTDataType x)
{
    LTNode* pur = pos->next;
    LTNode* newnode = LTBuyNode(x);
    pos->next = newnode;
    newnode->prev = pos;
    newnode->next = pur;
    pur->prev = newnode;
}

//删除指定位置
void LTErase(LTNode* pos)
{
    LTNode* before = pos->prev;
    LTNode* after = pos->next;
    before->next = after;
    after->prev = before;
    free(pos);
}

//查找某个节点
LTNode* LTFind(LTNode* phead, LTDataType x)
{
    LTNode* pur = phead->next;
    while (pur != phead)
    {
        if (pur->data == x)
        {
            return pur;
        }
        pur = pur->next;
    }
    return NULL;
}//找到则返回该节点的地址,找不到则返回空

3、test.c

c 复制代码
#include "ListNode.h"
void test()
{
	//测试初始化与检测哨兵位是否有效
	//LTNode* plist = LTInit();
	//printf("%d", LTEmpty(plist));
	
	//测试尾插及打印
	//LTPushBack(plist, 1);
	//LTPushBack(plist, 2);
	//LTPushBack(plist, 3);
	//LTPrint(plist);
	// 
	//测试头插
	//LTPushFront(plist, 5);
	//LTPrint(plist);
	// 
	//测试指定位置后插
	//LTInsert(plist->next->next, 8);
	// 
	// 测试尾删
	//LTPopBack(plist);
	//LTPrint(plist);
	// 
	// 测试头删
	//LTPopFront(plist);
	// 
	// 测试指定位置删
	//LTErase(plist->next->next);
	//LTPrint(plist);
	// 
	//测试查找某一位置
	//LTNode* n = LTFind(plist, 3);
	//printf("%p", n);

	//测试销毁链表
	//LTDestroy(plist);
}
int main()
{
	test();
	return 0;
}

调试结果








三、链表和顺序表的不同点

不同点 顺序表 链表
存储空间 逻辑和物理上连续 逻辑上连续,物理上不一定连续
随机访问 支持 不支持
任意位置插入或者删除元素 可能需要搬移元素,效率低 只需修改指针指向
插入 动态顺序表,空间不够时需要扩容 没有容量的概念
应用场景 元素高效访问/频繁访问 任意位置插入和删除频繁

今天的分享就到这里了~

相关推荐
Ajiang28247353041 小时前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
盼海1 小时前
排序算法(五)--归并排序
数据结构·算法·排序算法
幽兰的天空1 小时前
Python 中的模式匹配:深入了解 match 语句
开发语言·python
Theodore_10224 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
网易独家音乐人Mike Zhou4 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
zy张起灵4 小时前
48v72v-100v转12v 10A大功率转换电源方案CSM3100SK
经验分享·嵌入式硬件·硬件工程
冰帝海岸5 小时前
01-spring security认证笔记
java·笔记·spring
‘’林花谢了春红‘’5 小时前
C++ list (链表)容器
c++·链表·list
----云烟----6 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024066 小时前
SQL SELECT 语句:基础与进阶应用
开发语言