链表操作详解

简介: 链表操作详解

一、单链表和双向链表基本介绍

(1)什么是单链表

单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据 ————百度百科 单链表的访问都是由指针进行访问,不需要扩容,对于数据操作比较快速。

(2)什么是双链表

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表————百度百科双链表的访问就比单链表有优势,当数据量比较大时,想要对末尾的节点进行操作需要有个变量把前面节点全都遍历,但是双向链表只需要头节点的前驱就索引到目标了。

二、链表与顺序表比较

       顺序表的优点是数据存储是连续的,所以访问数据比较快速,但是要对顺序表元素进行操作,有时候会进行大量数据移动操作,是比较浪费时间的,而且扩容时有风险。

       链表的优点是利用碎片化空间,对于数据操作比较快,但是要进行遍历等操作时,速度是比较慢的,当释放内存操作失误很容易内存泄漏。

三、单链表基本操作

(1)单链表结构定义:

typedef int LTDataType;
typedef struct ListNode {
  struct ListNode* next;//后继指针指向下一个节点
  LTDataType data;//数据域存放数据
}LTNode;

(2)单链表初始化:

LTNode* InitNewNode(LTDataType n)
{
  LTNode* p = (LTNode*)malloc(sizeof(LTNode));//开辟新节点
  if (p == NULL)//检查节点是否开辟失败
  {
    perror("malloc");
    exit(-1);
  }
  p->next = NULL;//指针域置空
  p->data = n;//数据域赋值
  return p;//返回新节点
}

(3)单链表头插:

LTNode* LTPushFront(LTNode *head, LTDataType x)
{
  if (head == NULL)
    return NULL;
  LTNode* node = InitNewNode(x);//插入新节点
  node->next = head;//头插
  return node;//返回新的头节点
}

(4)单链表头删:

LTNode* LTPopFront(LTNode* head)
{
  if (head == NULL)
    return NULL;
  if (head->next)//链表节点>1的时候
  {
    LTNode* newhead = head->next;
    free(head);//头删
    return newhead;//返回新头节点
  }
  else//只有头节点的时候
  {
    free(head);
    return NULL;
  }
}

(5)单链表尾插:

LTNode* LTPushBack(LTNode *head, LTDataType x)
{
  if (head == NULL)//为空返回空
    return NULL;
  LTNode* node = InitNewNode(x);//尾插的新节点
  LTNode* p = head;
  while (p -> next)//遍历到最后一个节点
  p = p->next;
  p->next = node;//尾插
  node->next = NULL;
  return head;//返回头节点
}

(6)单链表尾删:

LTNode* LTPopBack(LTNode *head)
{
  if (head == NULL)
    return NULL;
  if (head->next == NULL)//只有头节点直接删除
  {
    free(head);
    return NULL;
  }
  LTNode* p = head;
  while (p->next->next)//遍历到最后一个节点的前一个节点
  {
    p = p->next;
  }
  LTNode* tmp = p->next;//最后一个节点保存
  p->next = tmp->next;//尾删
  free(tmp);
  return head;//返回链表头节点
}

(7)单链表pos位置插入:

LTNode* LTPush(LTNode* head, int pos, LTDataType val)
{
  if (pos == 0)//插入位置在0为头插
  {
    LTNode* node = InitNewNode(val);
    node->next = head;
    return node;
  }
  LTNode* node = InitNewNode(val);
  LTNode* p = head;
  for (int i = 0; i < pos - 1; i++)
  p = p->next;
  node->next = p->next;//进行插入
  p->next = node;
  return head;
}

(8)销毁单链表:

void LTDestroy(LTNode* head)
{
  if (head == NULL)
    return;
  for (LTNode* p = head, *q; p; p = q)
  {
    q = p->next;//保存p的下一个节点
    free(p);//销毁当前节点
  }
  return;
}

(9)单链表随机插入:

int main()
{
  srand((unsigned int)time(0));//产生伪随机
  LTNode* head = InitNewNode(rand() % 100 + 1);
  for (int i = 0; i < MAX_OP; i++)//MAX_OP为7,表示七次插入
  {
    int pos = rand() % (i + 2), val = rand() % 100;//插入位置与值随机
    printf("Insert %d at %d to Linklist \n", val, pos);
    head = LTPush(head, pos, val);
    output(head);//每次插入都打印
  }
  LTDestroy(head);
  return 0;
}

(10)单链表打印:

void output(LTNode* head)//我觉得这样会更美观一些
{
  int n = 0;
  for (LTNode* p = head; p; p = p->next)
    n++;
  for (int i = 0; i < n; i++)
  {
    printf("%3d", i);
    printf("  ");
  }
  printf("\n");
  for (int i = 0; i < n; i++)
  {
    printf("-----");
  }
  printf("\n");
  for (LTNode* p = head; p; p = p->next)
  {
    printf("%3d", p->data);
    printf("->");
  }
  printf("\n");
  printf("\n");
  printf("\n");
  return;
}

打印效果:

(11)单链表操作测试:

 

void Test(LTNode *head)
{
  printf("PushBack:\n");
  head = LTPushBack(head, 1);
  head = LTPushBack(head, 2);
  head = LTPushBack(head, 3);
  output(head);
  printf("PopBcak:\n");
  head = LTPopBack(head);
  head = LTPopBack(head);
  head = LTPopBack(head);
  output(head);
  printf("PushFront:\n");
  head = LTPushFront(head, 3);
  head = LTPushFront(head, 2);
  head = LTPushFront(head, 1);
  output(head);
  printf("PopFront:\n");
  head = LTPopFront(head);
  head = LTPopFront(head);
  head = LTPopFront(head);
  output(head);
  return;
}

打印结果:

四、双向链表基本操作

(1)双向链表结构定义:

typedef int LTDataType;
typedef struct ListNode {
  struct ListNode* next;//后继指针
  struct ListNode* pre;//前驱指针
  LTDataType data;
}LTNode;

(2)双链表初始化:

LTNode* BuyLTNode(LTDataType n)
{
  LTNode* p = (LTNode*)malloc(sizeof(LTNode));//开辟新节点
  p->pre = p;//前后指针都指向自己
  p->next = p;
  p->data = n;
  return p;//返回新节点
}

(3)双链表头插:

void LTPushFront(LTNode* phead, LTDataType n)
{
  assert(phead);//断言不为空指针
  LTNode* tail = phead->pre;//找到尾节点
  LTNode* node = BuyLTNode(n);
  node->next = phead;//新节点指向头指针
  phead->pre = node;//头指针前驱指向新节点
  tail->next = node;//尾节点指向新节点
  node->pre = tail;//新节点前驱指向尾节点
  return;
}

(4)双链表尾插:

void LTPushBack(LTNode* phead, LTDataType x)
{
  assert(phead);//断言是否为空指针
  LTNode* tail = phead->pre;//找到尾指针
  LTNode* newnode = BuyLTNode(x);
  tail->next = newnode;//尾指针指向新节点
  newnode->next = phead;//新节点指向头指针
  newnode->pre = tail;//新节点前驱指向尾指针
  phead->pre = newnode;//头节点前驱指向新节点
}

(5)双链表头删:

void LTPopFront(LTNode* phead)
{
  assert(phead);
  LTNode* tail = phead->pre;//保存尾节点
  tail->next = phead->next;//尾节点指向头节点下一个节点
  phead->next->pre = tail;//头节点下一个节点前驱指向尾节点
  free(phead);
  tail->next = phead;//尾节点指向新的头节点
  return;
}

(6)双链表尾删:

void LTPopBack(LTNode* phead)
{
  assert(phead);
  LTNode* tail = phead->pre;//找到尾节点
  tail->pre->next = phead;//尾节点前驱节点指向头节点
  phead->pre = tail->pre;//头节点前驱指向尾节点前驱
  free(tail);
  return;
}

(7)双链表pos位置插入:

void ListInsert(LTNode* pos, LTDataType x)
{
  assert(pos);
  LTNode* prev = pos->pre;//保存pos位置前一个节点
  LTNode* node = BuyLTNode(x);
  node->next = pos;//新节点指向pos
  pos->pre = node;//pos前驱指向新节点
  node->pre = prev;//新节点前驱指向pos前节点
  prev->next = node;//pos前节点指向新节点
  return;
}

(8)双链表删除节点:

void ListErase(LTNode* pos)
{
  assert(pos);
  LTNode* prev = pos->pre;//保存要删除位置前一个节点
  prev->next = pos->next;//前一个结点指向pos的下一个节点
  pos->next->pre = prev;//pos的下一个节点的前驱指向前一个节点
  return;
}

(9)双链表销毁:

void ListDestory(LTNode* phead)
{
  assert(phead);
  LTNode* p = phead->next;//保存头节点下一个节点
  while (p)
  {
    free(phead);
    if (p->next)
    {
      phead = p;
      p = p->next;
    }
  }
  return;
}

(10)双链表打印:

void ListPrint(LTNode* phead)//打印双链表
{
  assert(phead);
  LTNode* p = phead;
  do {
    printf("%d ", p->data);
    if (p->next != phead)
    {
      printf("<->");
    }
    p = p->next;
  } while (p != phead);
  printf("\n\n\n");
  return;
}

       如果这篇文章对您有帮助的话,还望三连支持一下作者啦~~

 

相关文章
|
6月前
特殊链表(循环单链表,循环双链表,静态链表)
特殊链表(循环单链表,循环双链表,静态链表)
50 3
|
6月前
单链表相关操作(头插法和尾插法)
单链表相关操作(头插法和尾插法)
57 4
不带头非循环的单向链表的增删查改的实现(代码版)
不带头非循环的单向链表的增删查改的实现(代码版)
|
存储
链表(一) 单链表操作详解
链表(一) 单链表操作详解
50 0
|
存储 算法 Java
Java数据结构与算法分析(三)链表(单链表、双链表、环形链表)
通过前篇文章《[数组](https://blog.csdn.net/gozhuyinglong/article/details/109702860)》了解到数组的存储结构是一块连续的内存,插入和删除元素时其每个部分都有可能整体移动。为了避免这样的线性开销,我们需要保证数据可以不连续存储。本篇介绍另一种数据结构:链表。
220 0
|
存储 算法 索引
【数据结构与算法】链表1:移除链表 &设计链表&链表反转(双指针法、递归法)
【数据结构与算法】链表1:移除链表 &设计链表&链表反转(双指针法、递归法)
87 0
链表带环问题
链表带环问题
103 0
|
存储
【链表】双向循环链表的实现
【链表】双向循环链表的实现
78 0
动态链表和静态链表的实现
动态链表和静态链表的实现
103 0
|
存储 API 索引
链表——双链表
双向链表也叫双向表,是链表的一种,它由多个结点组成,每个结点都由一个数据域和两个指针域组成,数据域用来存储数据,其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点。链表的头结点的数据域不存储数据,指向前驱结点的指针域值为null,指向后继结点的指针域指向第一个真正存储数据的结点
150 0
链表——双链表