408数据结构学习笔记——链表(一)

简介: 408数据结构学习笔记——链表

1.单链表

1.1.单链表的定义

92271379bc364bfda84385d9985c726e.png

  1. 优点:离散存放(不需要连续的存储空间),改变容量方便
  2. 缺点:不支持随机存储,需要额外空间存放指针
typedef struct LNode{    //定义单链表节点类型
    ElemType data;    //每个节点存放一个数据元素
    struct LNode *next;    //指针指向下个节点
}LNode, *linkList;

要表示单链表的时候,只需申明一个头指针L,指向单链表的第一个节点

//*Lnode L;    //强调是一个节点
linkList L;    //强调是一个链表,代码可读性更强

1.2.不带头结点的单链表

typedef struct LNode {    //定义单链表节点类型
  Elemtype data;    //每个节点存放一个数据元素
  struct LNode *next;    //指针指向下一个节点
}LNode, *linkList;
//初始化单链表
bool initList(linkList &L) {
  //将表置空,防止脏数据
  L = NULL;
  return true;
}
//判断表空
bool empty(linkList L){
    if (L == NULL) return true;
    else return false;
}
void main() {
  //申明一个指向单链表的指针
  linkList L;
  //初始化单链表
  initList(L);
  //后续代码
}

d48adc6e75cd4d77931b2ec18ee41bcb.png

1.3.带头结点的单链表

typedef struct LNode {
  Elemtype data;
  struct LNode *next;
}LNode, *linkList;
bool initList(linkList& L) {
  //分配一个头结点
  L = (LNode*)malloc(sizeof(LNode));
  //内存不足,分配失败
  if (L == NULL) return false;
  //当前表空,因此将头结点的next指针置空
  L->next = NULL;
  return true;
}
//判断表空
bool empty(linkList L){
    if (L->next == NULL) return true;
    else return false;
}
void main() {
  //申明一个指向单链表的指针
  linkList L;
  //初始化单链表
  initList(L);
  //后续操作
}

a813ebc0a0764a2eadf18b12add76fea.png

1.4.单链表的插入

1.4.1. 带头结点

bool listInsert(linkList& L, Elemtype e, int i) {
  //判断i值是否合法(判断是否过小)
  if (i < 1) return false;
  //申明一个LNode类型的指针*p,并使其指向linkList的头结点L
  LNode *p = L;
  //遍历单链表,找到要插入的位序
  for (int j = 0; p != NULL && j < i - 1; j++) p = p->next;
  //判断i值是否合法(判断是否过大)
  if (p == NULL) return false;
  //申明一个节点s,并为它分配内存空间和数据
  LNode* s = (LNode*)malloc(sizeof(LNode));
  s->data = e;
  //在单链表中插入s
  s->next = p->next;
  p->next = s;
  return true;
}

1.4.2.不带头结点

bool listInsert(linkList& L, int e, int i) {
  //判断i值是否合法
  if (i < 1) return false;
  //i=1时为特殊情况,在表头插入元素
  if (i == 1) {
    //申明一个节点s,并为其分配空间和输入数据
    LNode* s = (LNode*)malloc(sizeof(LNode));
    s->data = e;
    //将s插入表头,并且把L的指向改为s
    s->next = L;
    L = s;
    return true;
  }
  //申明一个LNode*类型的指针p指向第一个节点,并使用其遍历单链表
  LNode *p = L;
  for (int j = 1; p != NULL && j < i - 1) p = p->next;
  //i值不合法
  if (p == NULL) return false;
  //申明一个节点s,并为其分配空间和输入数据
  LNode* s = (LNode*)malloc(sizeof(LNode));
  s->data = e;
  //将s插入单链表中
  s->next = p->next;
  p->next = s;
  return true;
}

1.4.3.尾插法

//尾插法
bool insertNextNode(LNode *p, int e) {
  //i值不合法
  if (p == NULL) return false;
  LNode *s = (LNode*)malloc(sizeof(LNode));
  //内存分配失败
  if (s == NULL) return false;
  s->data = e;
  //插入s
  s->next = p->next;
  p->next = s;
  return true;
}
bool listInsert(linkList& L, int e, int i) {
  if (i < 1) return false;
  LNode *p = L;
  for (int j = 0; p != NULL && j < i - 1) p = p->next;
  //调用尾插法函数插入元素
  return insertNextNode(p, e);
}

1.4.4.前插法

//前插法
bool insertPriorNode(LNode *p, int e) {
  //i值不合法
  if (p == NULL) return false;
  LNode* s = (LNode*)malloc(sizeof(LNode));
  //在p后插入一个新节点s
  s->next = p->next;
  p->next = s;
  //将p的数据赋值给s,再将e赋值给p
  s->data = p->data;
  p->data = e;
  return true;
}

1.5.单链表的删除

1.5.1.按位序删除(带头结点)

//按位序删除
bool listDelete(linkList& L, int i, Elemtype& e) {
  //i值不合法
  if (i < 1) return false;
  //申明一个LNode类型的指针指向头结点
  LNode* p = L;
  //将指针指向第i - 1个元素
  for (int j = 0; p != NULL && j < i - 1; j++) p = p->next;
  //i值不合法
  if (p == NULL) return false;
  //第i个元素为空
  if (p->next == NULL) return false;
  //取回第i个元素的值
  LNode* q = p->next;
  e = q->data;
  p->next = q->next;
  free(q);
  return true;
}

1.5.2.指定节点删除

//指定节点删除
bool deleteNode(LNode* p) {
  //i值不合法
  if (p == NULL) return false;
  //申明一个节点q,并使其指向p->
  LNode* q = p->next;
  //将q的数据赋值给q
  p->data = q->data;
  //q的nxet指针赋值给q
  p->next = q->next;
  //释放q
  free(q);
  return true;
}

1.6.单链表按值查找

//按值查找
LNode* locateElem(linkList L, Elemtype e) {
  LNode* p = L->next;
  //从第一个元素遍历查找值为e的结点
  for (int i = 1; p != NULL && p->data != e) p = p->next;
  return p; //找到后返回该节点指针,否则返回NULL
}

1.7.求单链表长

//求表长
int length(linkList L) {
  //申明一个LNode*类型的指针p,使其指向表中第一个节点
  LNode* p = L->next;
  //标记长度
  int length = 0;
  //遍历单链表
  while (p != NULL) {
    p = p->next;
    length++;
  }
  return length;
}

1.8.单链表的建立

1.8.1.尾插法

linkList listTailInsert(linkList& L) {
  Elemtype x;
  //为单链表L开辟一个新的内存空间,作为头结点
  L = (linkList)malloc(sizeof(linkList));
  //申明两个指针,并让它们指向头结点
  LNode* i = L, * j = L;
  //输入数据
  cin >> x;
  //停止输入(0可以改为任意值)
  while (x != 0) {
    //开辟一个大小为LNode的内存空间,并让j指向它
    j = (LNode*)malloc(sizeof(LNode));
    //为j输入数据
    j->data = x;
    //将j插入单链表中
    i->next = j;
    //i后移至j
    i = j;
    //循环输出数据
    cin >> x;
  }
  //将最后一个节点的next置空
  i->next = NULL;
  return L;
}

1.8.2.头插法

linkList listHeadInsert(linkList& L) {
  //为头结点分配内存空间
  L = (linkList)malloc(sizeof(linkList));
  L->next = NULL;
  LNode* i;
  Elemtype x;
  cin >> x;
  whil(x != 0) {
    //为节点i分配内存空间,并输入数据
    i = (LNode*)malloc(sizeof(LNode));
    i->data = x;
    //将i插入表中
    i->next = L->next;
    L->next = i;
    cin >> x;
  }
  return L;
}

2.双链表

2.1.双链表的初始化

typedef struct DNode {
  Elemtype data;
  struct DNode* prior, * next;
}DNode, *DLinkList;
//初始化双链表
bool initDLinkList(DLinkList& L) {
  //为头结点分配空间
  L = (DLinkList)malloc(sizeof(DLinkList));
  //内存分配失败
  if (L == NULL) return false;
  //头结点的前后指针置空
  L->prior = NULL;
  L->next = NULL;
  return true;
}
void testDLinkList() {
  //申明一个双链表
  DLinkList L;
  //初始化双链表
  initDLinkList(L);
  //后续操作
}

2.2.双链表的插入

bool insertNode(DNode* p, DNode* s) {
  //插入操作非法
  if (p == NULL && s == NULL) return false;
  s->next = p->next;
  //特殊情况:p为双链表中最后一个节点
  if (p->next != NULL) p->next->prior = s;
  s->prior = p;
  p->next = s;
  return true;
}

2.3.双链表的删除、销毁

//删除双链表当前节点的下个节点
bool deleteNextDNode(DNode* p) {
  //删除操作非法
  if (p == NULL) return false;
  //申明一个DNode*类型的指针q指向p的下一个节点
  DNode* q = p->next;
  //删除操作非法
  if (q == NULL) return false;
  //改变前后指针,并且释放p的下一个节点q的内存空间
  p->next = q->next;
    //q不是最后一个节点
  if (q->next != NULL) q->next->prior = p;
  free(q);
  return true;
}
//销毁双链表
void destroyDLinkList(DLinkList &L){
    //遍历双链表,并且释放下个节点
    while(L->next != NULL) deleteNextDNode(L);
    //释放头结点
    free(L);
    //头指针L置空
    L = NULL;
}

2.3.双链表的删除、销毁

1.
//删除双链表当前节点的下个节点
bool deleteNextDNode(DNode* p) {
  //删除操作非法
  if (p == NULL) return false;
  //申明一个DNode*类型的指针q指向p的下一个节点
  DNode* q = p->next;
  //删除操作非法
  if (q == NULL) return false;
  //改变前后指针,并且释放p的下一个节点q的内存空间
  p->next = q->next;
    //q不是最后一个节点
  if (q->next != NULL) q->next->prior = p;
  free(q);
  return true;
}
//销毁双链表
void destroyDLinkList(DLinkList &L){
    //遍历双链表,并且释放下个节点
    while(L->next != NULL) deleteNextDNode(L);
    //释放头结点
    free(L);
    //头指针L置空
    L = NULL;
}

3.循环链表

typedef struct LNode {
  Elemtype data;
  struct LNode *next;
}LNode, *linkList;
//初始化循环链表
bool initList(linkList& L) {
  L = (linkList)malloc(sizeof(linkList));
  //内存分配失败
  if (L == NULL) return false;
  //将L的next指针指向自己
  L->next = L;
  return true;
}
//判断表空
bool isEmpty(linkList L) {
  if (L->next = L) return true;
  else return false;
}
//判断表尾
bool  isTail(LNode* p) {
  if (p->next == L) return true;
  else return false;
}

4.循环双链表

typedef struct DNode {
  elemtype data;
  struct DNode *prior, *next;
}DNode, DLinkList;
//初始化循环双链表
bool initDLinkList(DLinkList& L) {
  L = (DNode*)malloc(sizeof(DNode));
  if (L == NULL) return false;
  L->next = L;
  L->prior = L;
  return true;
}
//判断表空
bool isEmpty(DLinkList L) {
  if (L->next == L) return true;
  else return false;
}
//判断表尾
bool isTail(DLinkList L, DNode *p) {
  if (p->next == L) return true;
  else return false;
}
void testDLinkList() {
  DLinkList L;
  initDLinkList(L);
}

5.链表和顺序表的区别

5.1.逻辑结构

顺序表和链表都属于线性表

5.2.存储结构

  1. 顺序表
  1. 优点:存储密度大,支持随机存储
  2. 缺点:需要一片连续的存储空间,改变容量不方便
  1. 链表
  1. 优点:支持离散存储可以动态的修改容量
  2. 缺点:存储密度小(需要存放指针),不支持随机存储

5.3.基本操作

5.3.1.创建

  1. 顺序表:预分配一片连续的存储空间
  1. 静态分配:容量不可更改
  2. 动态分配:容量可以更改,但需要大量移动
  1. 链表:仅分配一个头结点(也可以仅声明一个头指针)

5.3.2.销毁

  1. 顺序表:修改length = 0,逻辑上销毁
  1. 静态分配:由系统自动回收
  2. 动态分配:需要手动free
  1. 链表:逐个free节点

5.3.3.增加/删除

  1. 顺序表:在增加/删除的位置后方逐一移动元素,时间复杂度为O(n)
  2. 链表:需找到该元素的所在位置,然后修改指针,时间复杂度为O(n)
  3. 虽然顺序表和链表的时间复杂度都为O(n),但是顺序表的操作为移动元素,链表的操作为查找元素,因此,顺序表所需时间远大于链表

5.3.4.查找

  1. 按位序查找
  1. 顺序表:由于有随机存储特性,因此,时间复杂度为O(1)
  2. 链表:需要按序遍历链表,因此,时间复杂度为O(n)

2.按值查找

  1. 顺序表:按序遍历顺序表,因此,时间复杂度为O(n),如果有序,可为O(lon2n)
  2. 链表:按序遍历链表,因此,时间复杂度为O(n)


相关文章
|
22天前
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
51 4
|
14天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
37 5
|
1月前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
62 4
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
22天前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
40 0
|
2月前
|
存储 Java
数据结构第三篇【链表的相关知识点一及在线OJ习题】
数据结构第三篇【链表的相关知识点一及在线OJ习题】
26 7
|
2月前
|
存储 安全 Java
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
26 3
|
2月前
|
算法 Java
数据结构与算法学习五:双链表的增、删、改、查
双链表的增、删、改、查操作及其Java实现,并通过实例演示了双向链表的优势和应用。
20 0
数据结构与算法学习五:双链表的增、删、改、查
|
1月前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
43 0