双向链表(笔记)

文章介绍了链表的两种常见形式:无头单向非循环链表和带头双向循环链表,并详细阐述了带头双向循环链表的结构特点和应用。提供了一段C语言实现带头双向循环链表的代码,包括初始化、插入、删除、查找等操作。同时对比了链表与顺序表在随机访问、插入删除效率和内存管理上的优缺点。
摘要由CSDN通过智能技术生成

一、链表的几种结构

1.单向或者双向链表

2.带头或者不带头(哨兵位的头结点,不存储有效数据)

3.循环或者非循环

 实际中最常用的是下面这两种:

①无头单向非循环链表

       无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等,另外这种结构在笔试面试中出现很多。

②带头双向循环链表

       带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

二、带头双向循环链表实现代码

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int LTDateType;
struct ListNode
{
  LTDateType data;
  struct ListNode* next;
  struct ListNode* prev;
}ListNode;
LTNode* ListInit();
void ListDestory(LTNode* phead);
void ListPrint(LTNode* phead);
void ListPushBack(LTNode* phead,LTDateType x);
void ListPushFront(LTNode* phead,LTDateType x);
void ListPopBack(LTNode* phead);
void ListPopFront(LTNode* phead);
LTNode* ListFind(LTNode* phead,LTDateType x);
void ListInsert(LTNode* pos,LTDateType x);
void ListErase(LTNode* pos);
#include "List.h"
void TestList1()
{
  LTNode* plist=ListInit();
  ListInit(plist);
  ListPushFront(plist,1);
  ListPushFront(plist,2);
  ListPushFront(plist,3);
  ListPushFront(plist,4);
  ListPrint(plist);
  ListPushBack(plist,1);
  ListPushBack(plist,2);
  ListPushBack(plist,3);
  ListPushBack(plist,4);
  ListPrint(plist);
  LTNode* pos=ListFind(plist,2);
  if(pos)
  {
     ListErase(pos);
  }
  ListPrint(plist);
  ListPopBack(plist,1);
  ListPopBack(plist,2);
  ListPopFront(plist,3);
  ListPopFront(plist,4);
  ListPrint(plist);
  ListDestory(plist);
  plist=NULL;
}
int main()
{
  TestList1();
  return 0;
}

#include "List.h"
//创建新结点,存放X的值
LTNode* BuyListNode(LTDateType x)
{
   LTNode* newnode=(LTNode*)malloc(sizeof(LTNode));//创建一个新结点
   newnode->data=x;//存放x的值
   newnode->next=NULL;
   newnode->pre=NULL;
   return newnode;
}
//链表初始化
LTNode* ListInit()
{
   //哨兵位头结点,哨兵位的头结点不存放数值
   LTNode* phead=(LTNode*)malloc(sizeof(LTNode));
   phead->next=phead;
   phead->prev=phead;//只有一个头结点
   return phead;
}
//打印
void ListPrint(LTNode* phead)
{
   assert(phead);
   LTNode* cur=phead->next;
   while(cur!=phead)
   {
     printf("%d",cur->data);//打印
     cur=cur->next;//指向下一个结点,直到返回头结点
   }
   printf("\n");
}
//尾插法
void ListPushBack(LTNode* phead,LTDateType x)
{
   assert(phead);
   LTNode* tail=phead->prev;
   LTNode* newnode=(LTNode*)malloc(sizeof(LTNode));//创建一个新结点
   newnode->data=x;//存放x的值
   tail->next=newnode;
   newnode->prev=tail;//建立tail和newnode的关系
   newnode->next=phead;
   phead->prev=newnode;//建立phead和newnode的关系
}
//尾删法,删的是phead的prev
void ListpopBack(LTNode* phead)
{
   assert(phead);
   assert(phead->next!=phead);
   LTNode* tail=phead->prev;//定义尾结点
   LTNode* pre=tail->prev;//定义尾结点的前驱
   pre->next=phead;
  phead->next=prev;//尾结点前驱和头结点的关系
   free(tail);//释放尾结点
}
//头插法
void ListPushFront(LTNode* phead,LTDateType x)
{
   assert(phead);
   LTNode* newnode=BuyListNode(x);
   LTNode* next=phead->next;//头结点的后继结点
   phead->next=newnode;
   newnode->prev=phead;//新结点和头结点的关系
   newnode->next=next;
   next->prev=newnode;//头结点后继和新结点的关系
}
//头删法
void ListPopFront(LTNode* phead)
{
   assert(phead);
   assert(phead->next!=phead);
   LTNode* next=phead->next;
   LTNode* nextNext=next->next;
   phead->next=nextNext;
   nextNext->prev=phead;
   free(next);
}
//查找
LTNode* ListFind(LTNode* phead,LTDateType x)
{
   assert(phead);
   LTNode* cur=phead->next;
   while(cur!=phead)
   {
     if(cur->data=x)
     {
       return cur;//找到,返回cur
     }
    cur=cur->next;//指向下一个结点
   }
   return NULL;//找不到,返回NULL
}
//pos位置之前插入
void ListInsert(LTNode* pos,LTDateType x)
{
   assert(pos);
   LTNode* prev=pos->prev;
   LTNode* newnode=BuyListNode(x);
   prev->next=newnode;//插入新结点
   newnode->prev=prev;
   newnode->next=pos;
   pos->prev=newnode;//定义新结点的前驱后继关系
}
//删除pos位置
void ListErase(LTNode* pos)
{
   assert(pos);
   LTNode* prev=pos->prev;//定义pos的前驱prev
   LTNode* next=pos->next;//定义pos的后继next
   prev->next=next;//前驱的下一个结点指向next
   next->prev=prev;//后继的前驱变为prev
}
//删除链表
void ListDestory(LTNode* phead)
{
   assert(phead);
   LTNode* cur=phead->next;
   while(cur!=phead)
   {
     LTNode* next=cur->next;
     free(cur);
     cur=next;//往下走
   }
   free(phead);
   phead=NULL;//传一级指针的话,置空不起作用
}
//传二级指针的原因,一级指针会出现野指针的危害

 三、顺序表和链表

顺序表
优点
1.支持随机访问,需要随机访问结构支持算法可以很好的适用。                                                       2.CPU高速缓存命中率更高。
缺点
1.头部中部插入删除时间效率低。0(N)
2.连续的物理空间,空间不够了以后需要增容。
a、增容有一定程度程度消耗。
b.为了避免频繁增容,一般我们都按倍数去增,
用不完可能存在一定的空间浪费

链表(双向带头循环链表)
优点
1、任意位置插入删除效率高。O(1)
2、按需申请释放空间。
缺点
1.不支持随机访问。(用下标访问)意味着:一些排序,二分查找等在这种结构上不适用。
2.链表存储一个值,同时要存储链接指针,也有一定的消耗。                                                        3.高速缓存的命中率更低。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值