【数据结构】双向链表


个人主页点这里~


双向链表

一、双向链表的结构

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;
}

调试结果








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

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

今天的分享就到这里了~

相关推荐
2401_857439692 小时前
SSM 架构下 Vue 电脑测评系统:为电脑性能评估赋能
开发语言·php
SoraLuna3 小时前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos
xlsw_3 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
Dream_Snowar4 小时前
速通Python 第三节
开发语言·python
XH华4 小时前
初识C语言之二维数组(下)
c语言·算法
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
冷眼看人间恩怨5 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
菜鸡中的奋斗鸡→挣扎鸡5 小时前
滑动窗口 + 算法复习
数据结构·算法
信号处理学渣5 小时前
matlab画图,选择性显示legend标签
开发语言·matlab
红龙创客5 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++