【数据结构】双向链表


个人主页点这里~


双向链表

一、双向链表的结构

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

调试结果








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

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

今天的分享就到这里了~

相关推荐
一休哥助手3 分钟前
Redis 五种数据类型及底层数据结构详解
数据结构·数据库·redis
这可就有点麻烦了4 分钟前
强化学习笔记之【TD3算法】
linux·笔记·算法·机器学习
落落落sss7 分钟前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
苏宸啊9 分钟前
顺序表及其代码实现
数据结构·算法
lin zaixi()13 分钟前
贪心思想之——最大子段和问题
数据结构·算法
夜雨翦春韭19 分钟前
【代码随想录Day30】贪心算法Part04
java·数据结构·算法·leetcode·贪心算法
简单.is.good24 分钟前
【测试】接口测试与接口自动化
开发语言·python
一直学习永不止步31 分钟前
LeetCode题练习与总结:H 指数--274
java·数据结构·算法·leetcode·数组·排序·计数排序
Yvemil744 分钟前
MQ 架构设计原理与消息中间件详解(二)
开发语言·后端·ruby
程序员是干活的1 小时前
私家车开车回家过节会发生什么事情
java·开发语言·软件构建·1024程序员节