[C语言 / 数据结构初阶]链表初阶

简介: [C语言 / 数据结构初阶]链表初阶

一、链表的概念


概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链 接次序实现的 。

1669437868074.jpg

注:1、从上图中可看出,链式结构在逻辑上是连续的,但是在物理上不一定连续。


2、从现实中的结点一般是通过malloc函数申请的,所以其内存分配是在堆区。


3、从堆上申请的空间,是按照一定的策略来分配的,则一个节点的大小为8个字节。


二、链表的分类


实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:

1. 单向或者双向链表


1669437886598.jpg


2. 带头或者不带头(是否有自带哨兵位头结点)


1669437894661.jpg


第二个链表的d1指向了我们的哨兵位头结点。


3. 循环或者非循环链表


1669437906238.jpg


4.无头单向非循环链表和带头双向循环链表


1669437918943.jpg


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


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


三、链表的实现(代码和注释)


无头+单向+非循环链表增删查改实现


头文件:

#pragma once 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//要求存储的数据从0开始,依次存储
//静态的顺序表
//问题:开小了,不够用,开大了,存在浪费。
//struct SeqList
//{
//  int a[N];
//  int size;//记录存储了多少个数据。
//};
typedef int SLDateType;//宏定义我们的 SLDateType是int类型的
//动态的顺序表
typedef struct SeqList
{
  SLDateType* a;
  int size; //存储数据个数
  int capacity;//存储空间大小
}SL, SeqList;
void SeqListPrint(SeqList* psl);//链表的打印
void SeqListInit(SeqList* psl);//链表的初始化
void SeqListDestroy(SeqList* psl);//链表的销毁
void SeqListCheckCapacity(SeqList* psl);//检查内存空间是否足够
//时间复杂度是o(1)
void SeqListPushBack(SeqList* psl, SLDateType x);//链表的尾插
void SeqListPopBack(SeqList* psl);//链表的尾删
//时间复杂度是o(n)
void SeqListPushFront(SeqList* psl, SLDateType x);//链表的头插
void SeqListPopFront(SeqList* psl);//链表的头删
void SeqListInsert(SeqList* psl, size_t pos, SLDateType x);
// 删除pos位置的数据
void SeqListErase(SeqList* psl, size_t pos);
// 顺序表查找
int SeqListFind(SeqList* psl, SLDateType x);

链表的函数实现部分的代码:

#include"SeqList.h"
#include<assert.h>
void SeqListPrint(SeqList* psl)//结构体指针传参
{
  for (int i = 0; i < psl->size; ++i)
  {
  printf("%d ", psl->a[i]);
  }
  printf("\n");
}
void SeqListInit(SeqList* psl)
{
  assert(psl);//断言psl不为空
  psl->a = NULL;//a就相当于是链表的头
  psl->size = 0;
  psl->capacity = 0;
}
void SeqListDestroy(SeqList* psl)//链表的删除
{
  assert(psl);
  free(psl->a);//free掉链表中a这个节点的位置
  psl->a = NULL;//将a指向空
  psl->capacity = psl->size = 0;//将链表的内存大小置为0
}
void SeqListCheckCapacity(SeqList* psl)//检查链表的内存,如果不够就增容。
{
  assert(psl);
  if (psl->size == psl->capacity)
  {
  //capacity == 0,所以要先特判一下capacity 的值
  size_t newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;//初始节点数为4,如果内存现在为0就扩大一倍
  SLDateType* tmp = realloc(psl->a, sizeof(SLDateType) * newCapacity);//申请空间
  if (tmp == NULL)
  {
    printf("realloc fail\n");
    exit(-1);
  }
  else
  {
    psl->a = tmp;
    psl->capacity = newCapacity;//原来的空间变为新空间
  }
  }
}
void SeqListPushBack(SeqList* psl, SLDateType x)
{
  //如果满了,要扩容
  if (psl->size == psl->capacity)
  {
  //capacity == 0,所以要先特判一下capacity 的值
  size_t newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
  SLDateType* tmp = realloc(psl->a, sizeof(SLDateType) * newCapacity);
  if (tmp == NULL)
  {
    printf("realloc fail\n");
    exit(-1);
  }
  else
  {
    psl->a = tmp;
    psl->capacity = newCapacity;
  }
  }
  psl->a[psl->size] = x;
  psl->size++;
}
void SeqListPopBack(SeqList* psl)
{
  assert(psl);
  if (psl->size > 0)
  { 
  psl->size--;//尾删就size--就好了
  }
}
void SeqListPushFront(SeqList* psl, SLDateType x)
{
  assert(psl);
  SeqListCheckCapacity(psl);
  int end = psl->size - 1;
  while (end >= 0)//所有的数据往后挪1位
  {
  psl->a[end + 1] = psl->a[end];
  --end;
  }
  psl->a[0] = x;//两种操作是等价的
  psl->size++;
  //SeqListInsert(psl, 0, x);在头部插入。
}
void SeqListPopFront(SeqList* psl)
{
  assert(psl);
  if (psl->size > 0)
  {
  int begin = 1;
  while (psl->size > begin)
  {
    psl->a[begin - 1] = psl->a[begin];
    ++begin;
  }
  --psl->size;
  }
}
void SeqListInsert(SeqList* psl, size_t pos, SLDateType x)
{
  // 暴力检查
  assert(psl);
  // 温和检查
  if (pos > psl->size)
  {
  printf("pos 越界:%d\n", pos);
  return;
  //exit(-1);
  }
  // 暴力检查
  //assert(pos <= psl->size);
  SeqListCheckCapacity(psl);
  //int end = psl->size - 1;
  //while (end >= (int)pos)
  //{
  //  psl->a[end + 1] = psl->a[end];
  //  --end;
  //}
  size_t end = psl->size;
  while (end > pos)
  {
  psl->a[end] = psl->a[end - 1];
  --end;
  }
  psl->a[pos] = x;
  psl->size++;
}


四、链表oj题(小试牛刀)


1、leetcode203移除链表元素力扣


1669437981118.jpg

1669437990154.jpg

包含三种情况


画个图分析:


思路:情况一和情况二都可以用prev和cur指针遍历数组的两个元素, if cur指针不等于6,prev指针和cur指针都往前走,如果cur = 6,prev跳到cur的下一个位置


如果是第三种情况,则需先找到head != val的位置,再重复进行如上操作。


代码示例:

struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode* prev = NULL;
    struct ListNode* cur = head;
    while(cur)
    {
        if(cur->val != val)//当cur这个位置的值不等于val时往下走
        {
            prev = cur;//prev跳到cur位置
             cur = cur->next;//cur指针继续往下走
        }
        else
        {
            struct ListNode* next = cur->next;//定义一个新的指针
            if(prev == NULL)//头删,head为空的状态
            {
                free(cur);
                head = next;//继续往后面走
                cur = next;
            }
            else
            {
                free(cur);//free掉cur这个节点
                prev->next = next;//跳过了cur这个点
                cur = next;//cur继续往后走
            }
        }
    }
    return head;
}


2、leetcode206反转链表力扣


1669438017787.jpg


思路1:反转指针方向


思路二:用三个指针, n1, n2, n3 分别存放NULL, head, head->next;


1669438026152.jpg


代码示例:


struct ListNode* reverseList(struct ListNode* head)
{
    if(head == NULL) return NULL;
    struct ListNode* n1, *n2, *n3;
    n1 = NULL;
    n2 = head;
    n3 = n2->next;//n3的地址是n2的下一位
    while(n2)
    {
        n2->next = n1;//n2 的下一位指向 n1;起到掉头的作用
        n1 = n2;
        n2 = n3;
        if(n3)
        n3 = n3->next;
    }
    return n1;
}


方法2:头插法


殊途同归。newhead 相当于之前的n1, cur = n2, next = n3;

struct ListNode* reverseList(struct ListNode* head)
{
    struct ListNode* NewHead = NULL;
    struct ListNode* cur = head;
    while(cur)
    {
        struct ListNode* next = cur->next;
        //头插
        cur->next = NewHead;//代表链表的指向方向。
        NewHead = cur;//接着把地址传过来(更新)
        cur = next;//(更新)
    }
    return NewHead;
}

3、leetcode 876链表的中间结点力扣


思路:快慢指针法


1669438063507.jpg


struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode* slow, * fast;
    slow = fast = head;//刚开始slow和fast指针都指向头
    while(fast && fast->next) //想的是结束的条件,写的是继续的条件
    {
        slow = slow->next;
        fast = fast->next->next;//fast 每次走两步
    }
    return slow;
}
相关文章
|
22天前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
34 1
|
1月前
|
存储 算法 搜索推荐
【趣学C语言和数据结构100例】91-95
本文涵盖多个经典算法问题的C语言实现,包括堆排序、归并排序、从长整型变量中提取偶数位数、工人信息排序及无向图是否为树的判断。通过这些问题,读者可以深入了解排序算法、数据处理方法和图论基础知识,提升编程能力和算法理解。
45 4
|
1月前
|
存储 机器学习/深度学习 搜索推荐
【趣学C语言和数据结构100例】86-90
本文介绍并用C语言实现了五种经典排序算法:直接插入排序、折半插入排序、冒泡排序、快速排序和简单选择排序。每种算法都有其特点和适用场景,如直接插入排序适合小规模或基本有序的数据,快速排序则适用于大规模数据集,具有较高的效率。通过学习这些算法,读者可以加深对数据结构和算法设计的理解,提升解决实际问题的能力。
43 4
|
1月前
|
存储 算法 数据处理
【趣学C语言和数据结构100例】81-85
本文介绍了五个经典算法问题及其C语言实现,涵盖图论与树结构的基础知识。包括使用BFS求解单源最短路径、统计有向图中入度或出度为0的点数、统计无向无权图各顶点的度、折半查找及二叉排序树的查找。这些算法不仅理论意义重大,且在实际应用中极为广泛,有助于提升编程能力和数据结构理解。
38 4
|
1月前
|
算法 数据可视化 数据建模
【趣学C语言和数据结构100例】76-80
本文介绍了五种图论算法的C语言实现,涵盖二叉树的层次遍历及广度优先搜索(BFS)和深度优先搜索(DFS)的邻接表与邻接矩阵实现。层次遍历使用队列按层访问二叉树节点;BFS利用队列从源节点逐层遍历图节点,适用于最短路径等问题;DFS通过递归或栈深入图的分支,适合拓扑排序等场景。这些算法是数据结构和算法学习的基础,对提升编程能力和解决实际问题至关重要。
46 4
|
1月前
|
存储 算法 vr&ar
【趣学C语言和数据结构100例】71-75
本文介绍了五个C语言数据结构问题及其实现,涵盖链表与二叉树操作,包括按奇偶分解链表、交换二叉树左右子树、查找节点的双亲节点、计算二叉树深度及求最大关键值。通过递归和遍历等方法,解决了理论与实际应用中的常见问题,有助于提升编程能力和数据结构理解。
37 4
|
1月前
|
存储 算法 C语言
【趣学C语言和数据结构100例】66-70
本书《趣学C语言和数据结构100例》精选了5个典型的数据结构问题及C语言实现,涵盖链表与数组操作,如有序集合的集合运算、有序序列表的合并、数组中两顺序表位置互换、三递增序列公共元素查找及奇偶数重排。通过详细解析与代码示例,帮助读者深入理解数据结构与算法设计的核心思想,提升编程技能。
32 4
|
20天前
|
存储 算法 C语言
【C语言】深入浅出:C语言链表的全面解析
链表是一种重要的基础数据结构,适用于频繁的插入和删除操作。通过本篇详细讲解了单链表、双向链表和循环链表的概念和实现,以及各类常用操作的示例代码。掌握链表的使用对于理解更复杂的数据结构和算法具有重要意义。
153 6
|
23天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
44 5
|
22天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
52 1
下一篇
DataWorks