My Gitee Link

数据结构笔记 — 线性表

线性表

 

 

  • 数据类型:一组性质相同的值得集合及定义在此集合上的一些操作的总称

  • 抽象数据类型 (Abstract Data Type, ADT) :是指一个数学模型及定义在该模型上的一组操作

  • 抽象数据类型的标准格式(伪代码)

1 ADT 抽象数据类型名
2 Data
3     数据元素之间逻辑关系的定义
4 Operation
5     操作
6 endADT

1 线性表

1.1 线性表的概念
  • 定义:由零个或者多个数据元素组成的 有限序列

    • 若元素存在多个,则第一个元素无前驱,而最后一个元素无后继,其他元素都有且只有 一个前驱一个后继

    • 线性表元素的个数 n(n>=0) 定义为线性表的长度,当 n=0 时,为空表

    • 线性表的抽象数据类型定义

      ADT 线性表(List)
      Data
          线性表的数据对象集合为{a1, a2, ..., an},每个元素的类型均为 DataType .其中,除第一个元素 a1 外,每一个元素有且只有一个直接前驱元素,除了最后一个元素 an 外,
        每一个元素有且只有一个直接后继元素, 且数据元素之间的关系是一对一的关系 Opreation InitList(
      *L):初始化操作,建立一个空的线性表 ListEmpty(L):判断线性表是否为空表,为空表返回 true,否则返回 false ClearList(*L):清空线性表 GetElem(L, i, *e):将线性表 L 中的第 i 个位置的元素值返回给 e LocateElem(L, e):在线性表 L 中查找与给定值 e 相等的元素,如果查找成功,返回该元素在表中的序号;否则,返回 0 表示失败 ListInsert(*L, i, e):在线性表 L 中第 i 个位置插入新元素 e ListDelete(*L, i, *e):删除线性表 L 中第 i 个位置的元素,并用 e 返回其值 endADT

      [例]   若存在两个集合 A 、B,求出两个集合 A 、B 的并集 A ∪ B

      [思路分析]

      循环遍历集合 B 中的每个元素,判断当前元素是否存在于 A 中,若不存在,将当前元素插入集合 A 中,最后得到的集合 A 即为 A ∪ B 的结果

      //伪代码如下
      void unionList(List *La, List Lb){
          int La_len, Lb_len, i;
          
          ElemType e;
          La_len = ListLength(*La);
          Lb_len = ListLength(*Lb);
          
          for (i = 1; i <= Lb_len; i++) {
              GetElem(Lb, i, *e);
              if (!LocateElem(*La, e)){
                  ListInsert(La, **La_len, e);
              }
          }
      }
1.2 线性表的顺序存储结构
  • 定义:指的是用一段地址连续的存储单元一次存储线性表的数据元素

  • 图示:线性表 (a1, a2, a3, ..., an) 的顺序存储如下所示: 

  • 线性表的顺序存储结构代码

    #define MAXSIZE 20
    typedef int ElemType;
    typedef struct{
        ElemType data[MAXSIZE];
        int length; //线性表当前长度
    } SqList;
  • 顺序存储结构封装需要三个属性

    • 存储空间的起始位置 ,数组 data ,它的存储位置就是线性表存储空间的存储位置

    • 线性表的 最大存储容量 :数组的最大长度 MaxSize

    • 线性表的 当前长度length

  • 地址的计算方法

    • 假设 ElemType 占用的存储单元大小为 c 字节,则对于线性表中第 i 个元素的存储位置可由 a1 推算得出:LOC(ai) = LOC(a1) + (i-1)*c ,其中 LOC() 为获取元素存储地址的函数

    • 通过这个公式,可以随时计算出线性表中任意元素的地址,且所用时间均相同,因此它的存储时间性能就是 O(1) ,通常称其为 随机存储结构    

  • 获得元素 (GetElem) 操作

    • 将线性表 L 中第 i 个未知的元素值返回,即返回数组中下标为 i-1 的值即可

      #define OK 1
      #define ERROR 0
      #define TRUE 1
      #define FALSE 0
      typedef int Status;
      // 初始条件:顺序线性表 L 已经存在, 1 <= i <= ListLength(L)
      // 操作结果:用 e 返回 L 中第 i 个元素的值Status GetElem(Sqlist L, int i, ElemType *e) {
          if (L.length == 0 || i < 1 || i > L.length) {
              return ERROR;
          }
          *e = L.data[i-1];
          return OK;
      }
  • 插入元素 ListInsert(*L, i, e) 操作

    • 如果插入位置不合理,抛出异常

    • 如果线性表长度大于等于数组长度,则抛出异常或者动态增加数组容量

    • 从最后一个元素向前开始遍历到第 i 个位置,分别后移一个位置

    • 将要插入的元素填入位置 i

    • 线性表长度 +1

      // 初始条件:顺序线性表 L 已经存在, 1 <= i <= ListLength(L)
      // 操作结果:在 L 中第 i 个位置之前插入新的元素数据 e,L 长度 +1
      ​
      Status ListInsert(SqlList *L, int i, ElemType) {
          int k;
          
          if (L->length == MAXSIZE) { //顺序线性表已满
              return ERROE;
          }
          if (i < 1 || i > L->length) {   //当 i 不在合法范围内
              return ERROR;
          }
          if (i <= L->length) {   //若数据插入位置不在表尾
              //将要插入位置以后的元素后移一位
              for (k = L->length-1; k >= i-1; k--) {
                  L->data[k+1] = L->data[k];
              }
          }
          
          L->data[i-1] = e;   //将新元素插入
          L->length++;
          
          return OK;
      }
  • 删除元素 ElemDelete() 操作

    • 如果删除的位置不合理,抛出异常

    • 取出删除元素

    • 从删除的元素位置开始遍历到最后一个元素位置,分别将他们都向前移动一个位置

    • 表长 -1

      // 初始条件:顺序线性表 L 已经存在, 1 <= i <= ListLength(L)
      // 操作结果:删除 L 中第 i 个位置的元素数据,并用 e 返回其值,L 长度 -1
      ​
      Status ListDelete(SqList *L, int i, ElemType *e) {
          int k;
          
          if (L->length == 0) {
              return ERROR;
          }
          if (i < 1 || i > L->length) {
              return ERROR;
          }
          
          *e = L->data[i-1];
          
          if (i < L->length) {
              for (k = i; k < L->length; k++) {
                  L->data[k-1] = L->data[k];
              }
          }
          
          L->length--;
          
          return OK;
      }
  • 线性表顺讯存储结构优缺点

    • 线性表的顺序存储结构,在存、读取数据时,不管是哪个位置,时间复杂度都是 O(1) ,而插入和删除操作,时间复杂度均为 O(n)

    • 顺序存储结构 比较适合元素个数比较稳定,不经常插入和删除元素,更多是存取数据的应用

    • 优点

      • 无须为表示表中元素之间的逻辑关条而增加额外的存储空间

      • 可以快速地存取表中任意位置的元素。

    • 缺点

      • 插入和删除操作需要移动大量元素

      • 当线性表长度变化较大时,难以确定存储空间的容量

      • 容易造成存储空间的“碎片”

1.3 线性表的链式存储结构
  • 定义:我们把存储数据元素信息的域称为 数据域 ,把存储直接后继位置的域称为 指针域 。指针域中存储的信息称为 指针或链 。这两部分信息组成数据元素称为 存储映像 ,称为 结点(Node)

  • 链表中的第一个结点的存储位置叫做 头指针 ,最后一个结点 指针为空 (NULL)

  • 头指针与头结点的异同

    • 头指针

      • 头指针是指链表指向第一个结点点的指针,若链表有头结点,则是指向头结点的指针

      • 头指针具有标识作用,所以常用头指针冠以链表的名字(指针变量的名字)

      • 无论链表是否为空,头指针均不为空

      • 头指针是链表的必要元素

    • 头结点

      • 头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(但也可以用来存放链表的长度)

      • 有了头结点,对在第一元素结点前插入结点和删除第一结点的操作与其它结点的操作就统一了

      • 头结点不一定是链表的必须要素 

  • 单链表存储结构

    typedef struct Node {
        ElemType data;      //数据域
        struct Node *Next;  //指针域
    } Node;
    typedef struct Node *LinkedList;
  • 单链表的读取操作

    • 声明一个结点 p 指向链表第一个结点,初始化 j1 开始

    • j<i 时,就遍历链表,让 p 的指针向后移动,不断指向一下结点,j+1

    • 若到链表末尾 p 为空,则说明第 i 个元素不存在

    • 否则查找成功,返回结点 p 的数据

      // 初始条件:顺序线性表 L 已经存在, 1 <= i <= ListLength(L)
      // 操作结果:用 e 返回 L 中第 i 个元素的值
      Status GetElem(ListedList L,
      int i, ElemType *e) { int j; LinkedList p; p = L->next; ++j; while (p && j < i) { p = p->next; ++j; } if (!p || j > i) { return ERROR; } *e = p->data; return OK; }
  • 单链表的插入操作

    【例】假设存储元素 e 的结点为 s ,要实现结点 pp->nexts 之前的逻辑关系,那么如何将结点 s 插入到 aiai+1 之间?

    【思路分析】分析可知,只需要对两个结点的指针进行适当的操作即可

    • 声明一结点 p 指向链表头结点,初始化 j1 开始
    • j < 1 时,就遍历链表,让 p 的指针向后移动,不断指向下一结点,j 累加 1
    • 若到链表末尾 p 为空,则说明第 i 个元素不存在
    • 否则查找成功,在条统中生成一个空结点 s
    • 将数据元素 e 赋值给 s->data
    • 返回成功
      // 初始条件:单链表 L 已经存在, 1 <= i <= ListLength(L)
      // 操作结果:在 L 中第 i 个位置之前插入新的数据元素 e, L 的长度 +1
      ​
      Status ListInsert(ListedList *L, int i, ElemType e) {
          int j;
          LinkedList p, s;
          
          p = *L;
          j = 1;
          
          while (p && j < i) {    //寻找第 i 个结点,如果找到, j = i,循环退出
              p = p->next;
              ++j;
          }
          
          if (!p || j > i) {  //一直到末尾没找到结点 i ,返回错误
              return ERROR;
          }
          
          s = (LinkedList)malloc(sizeof(Node));
          s->data = e;
          
          s->next = p->next;
          p->next = s;
          
          return OK;
      }
  • 单链表的删除操作

    • 思路很简单,p->next 绕过结点 q 直接指向结点 a3 即可

      // 初始条件:单链表 L 已经存在, 1 <= i <= ListLength(L)
      // 操作结果:删除单链表中第 i 个位置的元素,并用 e 返回其值,链表 L 的长度 -1
      ​
      Status ListedDelete(LinkedList *L, int i, ElemType *e) {
          int j;
          LinkedList p, q;
          
          p = *L;
          j = 1;
          
          while (p->next && j < i) {
              p = p->next;
              ++j;
          }
          
          if (!p->next || j > i) {
              return ERROR;
          }
          
          q = p->next;
          p->next = q->next;
          
          *e = q->data;
          free(q);
          
          return OK;
      }
    • 单链表结构优点:对于插入或者删除数据非常频繁的操作,单链表的效率优势更加明显
  • 1.4 单链表的整表创建
    • 创建单链表是一个动态生成链表的过程,从“空表”状态开始,依次建立元素结点并逐个插入链表

    • 算法思路如下

      • 声明一结点 p 和计数器变量 i

      • 初始化一空链表 L

      • L 的头结点的指针指向 NULL ,即建立一个带头结点的单链表

      • 循环实现后继结点的赋值和插入

    • 头插法建立单链表

      • 先让新节点的 next 指向头结点之后

      • 再让表头的 next 指向新节点

      • 程序流程图解

        //头插法建立单链表
        void CreatedLinkedListHead(LinkedList *L, int n) {
            LinkedList p;
            int i;
            
            srand(0); //初始化随机数种子
            
            *L = (LiskedLsit)malloc(sizeof(Node));
            (*L)->next = NULL;
            
            for (i = 0; i < n; i++) {
                p = (LinkedList)malloc(sizeof(Node));   //生成新节点
                p->data = rand() % 100 + 1;
                p->next = (*L)->next;
                (*L)->next = p;
            }
        }
    • 尾插法建立单链表

      • 程序流程图解

        //尾插法建立单链表
        void CreatedLinkedListTail(LinkedList *L, int n) {
            LinkedList p, q;    //q 为指向结点尾部的指针
            int i;
            
            srand(time(0));
            *L = (LinkedList)malloc(sizeof(Node));
            q = *L; //q 指向链表尾部
            
            for (i = 0; i < n; i++) {
                p = (Node *)malloc(sizeof(Node));
                p->data = rand() % 100 + 1;
                q->next = p;
                q = p;  // q 和 p 均为动态变量
            }
            q->next = NULL;
            
            return OK;
        }
    1.5 单链表的整表删除
    • 算法思路如下

      • 声明结点 pq

      • 将第一个结点赋值给 p ,下一个结点赋值给 q

      • 循环执行释放 p 和将 q 赋值给 p 的操作

        //单链表的整表删除
        ​
        Status ClearLinkedList(LinkedList *L) {
            LinkedList p, q;
            
            p = (*L)->next;
            
            while(p) {
                q = p->next;
                free(p);
                p = q;
            }
            
            (*L)->next = NULL;
            
            return OK;
        }
      • 常见的错误写法

        • 有人会觉得 q 变量没有存在的必要,只需要在循环体内直接写 free(p); p=p->next 即可,这样做后果如下:

        • 要知道 p 是一个结点,除了有数据域,还有指针域。当我们做 free(p) 时,其实是对整个结点点进行删除和内存释放的工作。而我们整表删除是需要一个个结点删除的,所以我们就需要 q 来记载 p 的下一个结点在内存中弄得位置

    1.6 单链表与顺序存储优缺点
    • 存储分配方式

      • 顺序存储结构用一段连续的存储单元依次存储线性表的数据元素

      • 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素

    • 时间性能

      • 查找

        • 顺序存储结构 O(1)

        • 单链表 O(n)

      • 插入和删除

        • 顺序存储结构需要平均移动表长一半的元素,时间为 O(n)

        • 单链表在计算出某位置的指针后,插入和删除射间仅为 O(1)

    • 空间性能

      • 顺序存储结构需要预分配存储空间,分大了,容易造成空间浪费;分小了,容易发生溢出

      • 单链表不需要分配存储空间,只要内存中有空间有就可以分配,元素个数也不受限制

    • 综上所述对比,有以下结论

      • 若线性表需要频繁查找,很少进行插入和删除操作射,宜采用顺序存储结构

      • 若需要频繁插入和删除时,宜采用单链表结构

    1.7 静态链表
    • 线性表的静态链表存储结构

      #define MAXSIZE 1000
      typedef struct {
          ElemType data;  //数据
          int cur;        //游标
      } Component, StaticLinkedList[MAXSIZE];
    • 对静态链表进行初始化相当于初始化数组

      Status InitStaticLinkedList(StaticLinkedList space) {
          int i;
          for (i = 0; i < MAXSIZE; i++) {
              space[i].cur = i + 1;
          }
          space[MAXSIZE - 1].cur = 0;
          
          return OK;
      }
    • 静态链表的特点

      • 数组的第一个和最后一个元素做特殊处理,他们的 data 不存放数据

      • 通常把未使用的数组元素称为备用链表

      • 数组的第一个元素,即下标为 0 的那个元素的 cur 就存放备用链表的第一个结点的下标

      • 数组的最后一个元素,即下标为 MAXSIZE-1cur 则存放第一个有数值的元素的下标,相当于单链表中的头结点作用

    • 静态链表的插入操作

      //获得空闲分量的下标
      int Malloc_SLL(StaticLinkedList space) {
          int i = space[0].cur;
          if (space[0].cur) {
              space[0].cur = space[i].cur;
              //把他的下一个分量作为备用
              return i;
          }
      }
      ​
      //在静态链表 L 中第 i 个元素之前插入新的数据元素 e
      Status StaticLinkedListInsert(StaticLinkedList L, int i, ElemType e) {
          int j , k , l;
          
          k = MAXSIZE - 1;
          if (i < 1 || i > ListLength(L) + 1) {
              return ERROR;
          }
          
          j = Malloc_SLL(L);
          
          if(j) {
              L(j).data = e;
              for (l = 1; l <= i-1; l++) {
                  k = L[k].cur;
              }
              L[j].cur = L[k].cur;
              L[k].cur = j;
              
              return OK;
          }
          return ERROR;
      }
    • 静态链表的删除操作
      /*[例] 要删除上表中的 C 元素,操作步骤如下:*/
      //删除 L 中的 第 i 个数据元素
      Status StaticLinkedListDelete(StaticLinkedList L, int i) {
          int j, k;
          
          if (i < 1 || i > ListLength(L)) {
              return ERROR;
          }
          
          k = MAXSIZE - 1;    //k = 1000 - 1 = 999
          
          for (j = 1; j <= i-1; j++) {
              k = L[k].cur;  //要删除的元素是 C,是第三个元素,因此 i = 3,循环执行 2 次
          }    //第一次, k = L[999].cur = 1; 第二次, k = L[1].cur = 5
          
          j = L[k].cur;    // j = L[5].cur = 2
          L[k].cur = L[j].cur; //L[5].cur = L[2].cur = 3
          
          Free_SLL(L, j);
          
          return OK;
      }
      
      //将下标为 k 的空闲链表回收到备用链表
      void Free_SLL(StaticLinkedList space, int k) { //K = 2
          space[k].cur = space[0].cur; //space[2].cur = space[0].cur = 6
          space[0].cur = k; //space[0].cur = k = 2
      }
      
      //返回 L 中数据元素的个数
      int ListLength(StaticLinkedist L) {
          int j = 0;
          int i = L[MAXSIZE - 1].cur;
          
          while (i) {
              i = L[i].cur;
              j++;
          }
          return j;
      }

      最终的链表形式如下:

  • 静态链表优缺点总结

    • 优点

      • 在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点

    • 缺点

      • 没有解决连续存储分配(数组)带来的表长难以确定的问题

      • 失去了顺序存储结构随机存取的特性

      • 静态链表其实是为了给没有指针的编程语言设计的一种实现单链表功能的方法

    【例】腾讯面试题:快速找到未知长度单链表的中间节点

    • 一般思路:从头节点遍历表长,得出单链表长度 L,然后再遍历单链表,找到 L / 2,其时间复杂度为 O(3n/2)

    • 快慢指针原理(标尺思想):设置两个指针 *search、*mid 都指向单链表的头结点。其中 *search 的移动速度是 *mid 的两倍。则当 *search 指向末尾结点的时候, *mid 刚好指向中间结点(时间复杂度为 O(n/2)

      Status GetMidNode(Linkedist L, ElemType *e) {
          LinkedList search, mid;
          mid = search = L;
          
          while(search->next != NULL) {
              if (search->next->next != NULL) {
                  search = search->next->next;
                  mid = mid->next;
              }else {
                  search = search->next;
              }
          }
          
          *e = mid->data;
          
          return OK;
      }
1.8 循环链表
  • 定义:将单链表中的终端结点的指针指向头结点,形成一个闭合的环,该种形式的链表称之为循环链表

  • 循环链表不一定要有头结点 head ,判空条件为 if (head->next == head)

  • 循环链表相关操作

    • 循环链表的初始化

      //循环链表结构体定义
      typedef struct ClinkedList{
          int data;
          struct ClinkedList *next;
      } Node;
      ​
      //初始化循环链表
      void InitCircularLinkedList(Node **pNode) {
          int item;
          Node *temp, target;
          
          printf("输入节点的值,输入 -1 完成初始化\n");
          
          while(1){
              scanf("%d", &item);
              fflush(stdin);
              
              if (item == -1){return;}
              
              if ((*pNode) == NULL){
                  //循环链表中只有一个结点
                  *pNode = (Node *)malloc(sizeof(CLinkedList));
                  if (!(*pNode)){
                      exit(0);
                  }
                  (*pNode)->data = item;
                  (*pNode)->next = *pNode;
              }
              else{//找到next指向第一个结点的结点
                  for (target = (*pNode); target->next != (*pNode); target = target->next){
                      ;
                  }
                  
                  if (!temp){
                      exit(0);
                  }
                  temp->data = item;
                  temp->next = *pNode;
                  target->next = temp;
              }
          }
      }
    • 循环链表的插入

      //循环链表的插入
      void InsertCricularLinkedList(Node **pNode, int i) {
          Node *temp, *target, *p;
          int item, j;
          
          printf("请输入要插入结点的值:");
          scanf("%d", &item);
          
          if(i = 1){
              //新插入的结点作为第一个结点
              temp = (Node *)malloc(sizeof(CLinkedList));
              
              if(!temp){ exit(0);}
              temp->data = item;
              
              //寻找最后一个结点
              for(target = (*pNode); target->next != (*pNode); target = target->next){ ;}
              temp->next = (*pNode);
              target->next = temp;
              *pNode = temp;
          }
          else{
              target = *pNode;
              for(j = 1; j < (i-1); j++){
                  target = target->next;
              }
              
              temp = (Node *)malloc(sizeof(CLinkedList));
              
              if(!temp){ exit(0); }
              
              temp->data = item;
              p = target->next;
              target->next = temp;
              temp->next = p;
          }
      }
    • 循环链表删除结点

      //删除结点
      void DeleteCircularLinkedList(Node **pNode, int i){
          Node *target, temp;
          int j;
          
          if(i = 1){
              //删除第一个结点
              for(target = *pNode; target->next != *pNode; target = target->next){ ; }
              temp = *pNode;
              *pNode = (*pNode)->next;
              target->next = *pNode;
              free(temp);
          }
          else{
              target = *pNode;
              
              for(j = 1; j < i-1; j++){
                  target = target->next;
              }
              temp = target->next;
              target->next = temp->next;
              free(temp);
          }
      }
    • 返回结点所在的位置

      //元素查找
      int SearchCircularLinkedList(Node *pNode, int elem){
          Node *target;
          int i = 1;
          
          for(target = *pNode; target->data != elem && target->next != pNode; i++){
              target = target->next;
          }
          if(target->next == pNode){ //表中不存在该元素
              return 0;
          }
          else{
              return i;
          }
      }
1.9 约瑟夫问题

据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39个犹太人与Josephus及他的朋友躲到一个润中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止

使用循环链表模拟约瑟夫问题,把 41 个人自杀的顺序编号输出

#include <stdio.h>
#include <stdlib.h>
​
typedef struct Node{
    int data;
    struct Node *next;
} Node;
​
Node *Creat(int n){
    Node *p = NULL; *head;  //p表示当前结点,动态指针
    head = (Node *)malloc(sizeof(Node));
    p = head;
    Node *s;
    int i = 1;
    
    if( 0!= n){
        while(i <= n){
            s = (Node *)malloc(sizeof(Node));
            s->data = i++;
            p->next = s;
            p = s;
        }
        s->next = head->next;
    }
    free(head);
    
    return s->next;
}
​
​
int main(){
    int n = 41;
    int m = 3;
    int i;
    Node *p = Creat(n);
    Node *temp;
    
    m %= n;
    
    while(p != p->next){
        for(i = 1; i < m - 1; i++){
            p = p->next;
        }
        printf("%d->", p->next->data);
        temp = p->next;
        p->next = temp->next;
        
        free(temp);
        
        p = p->next;
    }
    printf("%d\n",p->data);
    
    return 0;
}

【例 1】将两个循环链表连接成一个循环链表

//A、B 均为非空循环链表的尾指针
LinkedList Connect(LinkedList A, LinkedList B){
    LinkedList p = A->next;     //保存 A 表头结点位置
    A->next = B->next->next;    //B 表的开始结点连接到 A 表尾
    free(B->next);              //释放 B 的头结点
    B->next = p;
    return B;                   //返回循环链表的尾指针
}

【例 2】判断单链表中是否有环

双指针法:使用p、q两个指针,p总是向前走,但q每次都从头开始走,对于每个节点,看p走的步数是否和q一样 快慢指针法:使用p、q两个指针,p每次向前走一步,q每次向前走两步,若在某个时候p==q,则存在环

1.10 双向链表
  • 双向链表结点结构

    typedef struct DualNode{
        ElemType data;
        struct DualNode *prior; //前驱结点
        struct DualNode *next;  //后继结点
    } DualNode, *DuLinkedList;

  • 插入操作

    s->next = p;
    s->prior = p->next;
    p->prior->next = s;
    p->prior = s;
  • 删除操作

    p->prior->next = p->next;
    p->next->prior = p->prior;
    free(p);

     

 

   >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 我是一条分割线 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

 

1 顺序表基本功能的实现

  1 // File name is LinkedListSummary.c
  2 // Created by YYH on 2020/12/9 11:57
  3 
  4 #include <stdio.h>
  5 #include <stdlib.h>
  6 #include <time.h>
  7 #define MAXSIZE 5
  8 #define OK 1
  9 #define ERROR 0
 10 typedef _Bool Status ;
 11 typedef int ElemType ;
 12 
 13 //顺序表结构体定义
 14 typedef struct {
 15     ElemType data[MAXSIZE]; //顺序表元素
 16     int length;     //顺序表当前长度
 17 } SqList;
 18 
 19 //打印当前的顺序表元素
 20 void DisplaySqList(SqList *L){
 21     printf("当前顺序表为 : ");
 22     for (int i = 0; i < L->length; ++i) {
 23         printf("%d ", L->data[i]);
 24     }
 25 }
 26 
 27 //初始化顺序表
 28 SqList *InitSqList(){
 29     SqList *L;
 30     L = (SqList *)malloc(sizeof(SqList));   //分配内存空间
 31     L->length = 0;
 32     srand((unsigned)time(NULL));    //借用随机数生成顺序表元素
 33     for (int i = 0; i < MAXSIZE; i++) {
 34         L->data[i] = rand() % 100 + 1;
 35         L->length++;
 36     }
 37     DisplaySqList(L);
 38     return L;
 39 }
 40 
 41 //获取元素
 42 Status GetElemSqList(SqList *L) {
 43     int UserSub, UserNum;
 44     printf("\n请输入要获取元素的位置(1-%d):", MAXSIZE);
 45     scanf("%d", &UserSub);
 46     if (L->length == 0 || UserSub < 1 || UserSub > L->length) {
 47         return ERROR;
 48     }
 49     UserNum = L->data[UserSub-1];
 50     printf("当前顺序表第[%d]个位置的元素是[%d]", UserSub, UserNum);
 51     return OK;
 52 }
 53 
 54 //插入元素
 55 Status InsertSqList(SqList *L) {
 56 
 57     int k, UserSub, InsertNum;
 58     printf("\n请输入要插入元素的位置:");
 59     scanf("%d", &UserSub);
 60     printf("\n请输入要插入的元素:");
 61     scanf("%d", &InsertNum);
 62 
 63     if (UserSub < 1 || UserSub > L->length) {
 64         return ERROR;
 65     }
 66 
 67     if (UserSub <= L->length){
 68         for (k = L->length-1; k >= UserSub-1; k--){
 69             L->data[k+1] = L->data[k];
 70         }
 71     }
 72     L->data[UserSub-1] = InsertNum;
 73     L->length++;
 74 
 75     DisplaySqList(L);
 76     return OK;
 77 }
 78 
 79 //删除元素
 80 Status DeleteSqList(SqList *L){
 81     int k;
 82     int UserSub, DeleteNum;
 83     printf("\n请输入要删除元素的位置:");
 84     scanf("%d", &UserSub);
 85     if (L->length == 0 || UserSub < 1 || UserSub > L->length) {
 86         return ERROR;
 87     }
 88     DeleteNum = L->data[UserSub-1];
 89 
 90     if (UserSub < L->length){
 91         for (k = UserSub; k < L->length; k++){
 92             L->data[k-1] = L->data[k];
 93         }
 94     }
 95     L->length--;
 96 
 97     DisplaySqList(L);
 98     return OK;
 99 }
100 
101 
102 int main(){
103 
104     SqList *InitList = InitSqList();
105     GetElemSqList(InitList);
106     InsertSqList(InitList);
107     DeleteSqList(InitList);
108 
109     return 0;
110 }

2 链表基本功能的实现

  1 // File name is LinkList.c
  2 // Created by YYH on 2020/12/10 14:03
  3 
  4 #include <stdio.h>
  5 #include <stdlib.h>
  6 
  7 #define LL_SIZE 5
  8 typedef int ElemType ;
  9 
 10 //单链表结构
 11 struct Node {
 12     ElemType data;
 13     struct Node *next;
 14 };
 15 typedef struct Node LNode;
 16 typedef struct Node *LinkedList;
 17 
 18 LinkedList CreatLinkedListTail(ElemType a[LL_SIZE]);
 19 int LengthLinkedList(LinkedList L);
 20 void DisplayLinkedList(LinkedList L);
 21 void GetElemLinkedList(LinkedList L);
 22 void InsertLinkedList(LinkedList L);
 23 void InsertLinkedList(LinkedList L);
 24 void DeleteLinkedList(LinkedList L);
 25 
 26 
 27 //尾插法创建单链表
 28 LinkedList CreatLinkedListTail(ElemType a[LL_SIZE]){
 29     LinkedList p, L, tail;
 30     L = (LNode *)malloc(sizeof(LNode));
 31     tail = L;
 32 
 33     for (int i = 0; i < LL_SIZE; i++){
 34         p = (LNode *)malloc(sizeof(LNode));
 35         p->data = a[i];
 36         tail->next = p;
 37         tail = p;
 38     }
 39     tail->next = NULL;
 40     return L;
 41 }
 42 
 43 //求表长
 44 int LengthLinkedList(LinkedList L){
 45     LNode *p;
 46     p = L->next;
 47     int i = 0;
 48     while (p != NULL){
 49         i++;
 50         p = p->next;
 51     }
 52     return i;
 53 }
 54 
 55 //打印单链表
 56 void DisplayLinkedList(LinkedList L){
 57     LinkedList p;
 58     p = L->next;
 59     printf("当前链表为:");
 60     while (p != NULL){
 61         printf("%d ", p->data);
 62         p = p->next;
 63     }
 64 }
 65 
 66 //单链表的读取操作
 67 void GetElemLinkedList(LinkedList L){
 68     int UserSub, j, UserNum;
 69     LinkedList p;
 70 
 71     p = L->next;
 72     j = 1;
 73 
 74     printf("\n\n>>>>>>>>>> 单链表读取元素 <<<<<<<<<<\n\n");
 75     printf("请输入要查询元素的位置(1-%d):", LengthLinkedList(L));
 76     scanf("%d", &UserSub);
 77     while (p && j < UserSub){
 78         p = p->next;
 79         ++j;
 80     }
 81     if (!p || j > UserSub){
 82         printf("查询位置错误!");
 83         exit(0);
 84     }
 85     UserNum = p->data;
 86     printf("链表中位置%d上的元素为%d", UserSub, UserNum);
 87 }
 88 
 89 //单链表的插入操作
 90 void InsertLinkedList(LinkedList L){
 91     int j,UserSub, UserNum;
 92     LinkedList p, s;
 93 
 94     p = L;
 95     j = 1;
 96 
 97     printf("\n\n>>>>>>>>>> 单链表插入元素 <<<<<<<<<<\n\n");
 98     printf("请输入插入元素在链表中的位置(1-%d):", LengthLinkedList(L));
 99     scanf("%d", &UserSub);
100     printf("请输入插入位置的元素:");
101     scanf("%d", &UserNum);
102 
103     while (p && j < UserSub){
104         p = p->next;
105         j++;
106     }
107     if (!p || j > UserSub){
108         printf("输入位置有误!");
109         exit(0);
110     }
111 
112     s = (LinkedList *)malloc(sizeof(LNode));
113     s->data = UserNum;
114 
115     s->next = p->next;
116     p->next = s;
117 
118     DisplayLinkedList(L);
119 }
120 
121 //单链表的删除操作
122 void DeleteLinkedList(LinkedList L){
123     int j, UserSub, UserNum;
124     LinkedList p, q;
125 
126     printf("\n\n>>>>>>>>>> 单链表删除元素 <<<<<<<<<<\n\n");
127     printf("请输入要删除元素在链表中的位置(1-%d):", LengthLinkedList(L));
128     scanf("%d", &UserSub);
129 
130     p = L;
131     j = 1;
132     while (p->next && j < UserSub){
133         p = p->next;
134         j++;
135     }
136     if (!p->next || j > UserSub){
137         printf("输入位置有误!");
138         exit(0);
139     }
140 
141     q = p->next;
142     p->next = q->next;
143 
144     UserNum = q->data;
145     printf("删除的元素为链表第%d个位置上的%d\n", UserSub, UserNum);
146     DisplayLinkedList(L);
147     free(q);
148 }
149 
150 
151 int main(){
152     //创建单链表
153     ElemType a[LL_SIZE], i;
154     LinkedList L;
155     printf(">>>>>>>>>> 单链表创建 <<<<<<<<<<\n\n");
156     printf("请输入%d个链表元素,元素之间使用空格,回车结束:\n", LL_SIZE);
157     for (i = 0; i < LL_SIZE; i++){
158         scanf("%d", &a[i]);
159     }
160     L = CreatLinkedListTail(a);
161     DisplayLinkedList(L);
162 
163     //查询单链表元素
164     GetElemLinkedList(L);
165 
166     //插入元素
167     InsertLinkedList(L);
168 
169     //删除元素
170     DeleteLinkedList(L);
171 
172 
173 }

 

>>>>> END <<<<<

posted @ 2020-12-07 17:46  笨草戆沐  阅读(460)  评论(0)    收藏  举报