考研 | 数据结构【第二章】线性表

考研 | 数据结构【第二章】线性表

I. 线性表

  1. 定义: 线性表是具有相同数据类型的 n   ( n ≥ 0 ) n\ (n\geq0) n (n0) 个数据元素的有限数列, 其中 n n n 为表长
  2. 基本操作:
    InitList(&L): 初始化表. 构造一个空的线性表L, 分配内存空间
    DestroyList(&L): 销毁表并释放线性表L所占用的内存空间.
    ---
    ListInsert(&L, i, e): 插入操作. 在表L中第i个位置插入指定元素e.
    ListDelete(&L, i, &e): 删除操作. 删除表L中第i个元素, 并用参数e返回删除元素的值.
    ---
    LocateElem(L, e): 按值查找
    GetElem(L, i): 按位查找, 获取表L中第i个位置的元素的值
    ---
    Length(L): 求表长
    PrintList(L): 从前到后输出线性表L中所有元素
    Empty(L): 判空操作
    

II. 顺序表

a. 定义

  1. 定义: 用 顺序存储 的方式实现 线性表 顺序存储. 把逻辑上相邻的元素存储在物理位置上也 相邻 的存储单元中.
  2. 静态分配:
    #include <stdio.h>
    #define MaxSize 10
    typedef struct {
          
          
    	int data[MaxSize];
    	int length;
    }SqList;
    
    void InitList(SqList &L) {
          
          
    	for(int i=0; i<MaxSize; i++)
    		L.data[i] = 0;
    	L.Length = 0;
    }
    
    int main() {
          
          
    	SqList L;
    	InitList(L);
    	...
    	return 0;
    }
    
  3. 动态分配:
    // 定义动态顺序表
    typedef struct {
          
          
        int *data;
        int maxSize;
        int lenght;
    }seqDList;
    // 初始化动态顺序表
    void initDList(seqDList &L) {
          
          
        L.data = (int *)malloc(MaxSize*sizeof(int));
        L.maxSize = dftMaxSize;
        L.lenght = 0;
    }
    // 扩充动态顺序表
    void increaseDList(seqDList &L, int len) {
          
          
        int* p = L.data;
        L.data = (int *)malloc((L.maxSize+len)*sizeof(int));
        for (int i=0; i<L.lenght; i++) {
          
          
            L.data[i] = p[i];
        }
        L.maxSize = L.maxSize + len;
        free(p);
    }
    
  4. 特点:
    1. 随机访问, 时间复杂度 O ( 1 ) O(1) O(1)
    2. 存储密度高, 每个结点只存储数据元素
    3. 扩展不方便
    4. 插入, 删除操作不方便, 需要移动大量元素

b. 插入和删除

  1. 插入:
    // 在第i位插入元素
    bool insertSList(seqSList &L, int i, int e) {
          
          
        if (i<1 || i>L.length+1)
            return false;
        if (L.length >= dftMaxSize)
            return false;
        for (int j=L.length; j >= i; j--)
            L.data[j] = L.data[j-1];
        L.data[i-1] = e;
        L.length++;
        return true;
    }
    
    时间复杂度: 最好 O ( 1 ) O(1) O(1), 最坏 O ( n ) O(n) O(n), 平均 O ( n ) O(n) O(n)
  2. 删除:
    // 删除第i位元素
    bool deleteSList(seqSList &L, int i, int &e) {
          
          
        if (i<1 || i>L.length)
            return false;
        e = L.data[i-1];
        for (int j=i; j<L.length; j++)
            L.data[j-1] = L.data[j];
        return true;
    }
    
    时间复杂度: 最好 O ( 1 ) O(1) O(1), 最坏 O ( n ) O(n) O(n), 平均 O ( n ) O(n) O(n)

c. 查找

  1. 按位查找:
    // 按位查找
    int GetElem(seqSList &L, int i) {
          
          
        return L.data[i-1]
    }
    
    时间复杂度 O ( 1 ) O(1) O(1)
  2. 按值查找:
    // 按值查找
    int LocateElem(seqSList &L, int e) {
          
          
        for (int i=0; i<L.length; i++)
            if (L.data[i]==e)
                return i+1;
        return 0;
    }
    
    时间复杂度: 最好 O ( 1 ) O(1) O(1), 最坏 O ( n ) O(n) O(n), 平均 O ( n ) O(n) O(n)

III. 单链表

a. 定义

用 链式存储 的存储结构进行存储的线性表叫做单链表.
在这里插入图片描述

  1. 带头结点的建立和初始化
    typedef struct LNode {
          
          
        int data;
        struct LNode *next;
    }LNode, *linkList;
    
    // 带头结点初始化和判空
    bool initList_h(linkList &L) {
          
          
        if (L==NULL)
            return false;
        L->next = NULL;
        return true;
    }
    bool empty_h(linkList &L) {
          
          
        if (L->next == NULL)
            return true;
        else
            return false;
    }
    
  2. 不带头节点的建立和初始化
    // 不带头节点初始化和判空
    bool initList(linkList &L) {
          
          
        L->next = NULL;
        return true;
    }
    bool empty(linkList L) {
          
          
        return L==NULL;
    }
    

b. 插入

  1. 带头结点的按位序插入: 时间复杂度 O ( n ) O(n) O(n)
    // 带头结点按位序插入
    bool listInsert(linkList &L, int i, int e) {
          
          
        if (i < 1)          // i 代表的是第i个结点
            return false;
        LNode *p;
        int j = 0;
        p = L;
        while (p!=NULL && j<i-1) {
          
            // 令p指到第i-1个结点位置采用后插法
            p = p->next;
            j++;
        }
        if (p == NULL)
            return false;
        LNode *s = (LNode *)malloc(sizeof(LNode));
        s->data = e;
        s->next = p->next;
        p->next = s;
        return true;
    }
    
  2. 不带头节点的按位序插入: 时间复杂度 O ( n ) O(n) O(n)
    // 不带头节点按位序插入
    bool listInsert(linkList &L, int i, int e) {
          
          
        if (i < 1)
            return false;
        if (i == 1) {
          
          
            LNode *s = (LNode *)malloc(sizeof(LNode));
            s->data = e;
            s->next = L;
            L = s;
            return true; 
        }
        LNode *p = L;
        int j = 1;
        while (p!=NULL && j < i-1) {
          
          
            p = p->next;
            j++;
        }
        if (p == NULL)
            return false;
        LNode *s = (LNode *)malloc(sizeof(LNode));
        s->data = e;
        s->next = p->next;
        p->next = s;
        return true;
    }
    
  3. 指定结点的后插操作: 时间复杂度 O ( 1 ) O(1) O(1)
    // 指定节点的后插操作
    bool inserNextNode(LNode *p, int e) {
          
          
        if (p == NULL)
            return false;
        LNode *s = (LNode *)malloc(sizeof(LNode));
        if (s == NULL)
            return false;
        s->data = e;
        s->next = p->next;
        p->next = s;
        return true;
    }
    
  4. 指定结点的前插操作: 时间复杂度位 O ( 1 ) O(1) O(1) 版本
    本质上还是 后插法, 只不过是将新的结点插到 p p p 的后面, 然后令新节点的数据等于 p p p 的数据, 而 p p p 的数据又等于新的数据, 从而达到一个 “前插” 的效果.
    // 指定结点的前插操作 O(1)
    bool inserPriorNode(LNode *p, int e) {
          
          
        if (p == NULL)
            return false;
        LNode *s = (LNode *)malloc(sizeof(LNode));
        if (s == NULL)
            return false;
        s->next = p->next;
        p->next = s;
        s->data = p->data;
        p->data = e;
        return true;
    }
    

c. 删除

  1. 带头节点按位序删除: 平均时间复杂度 O ( n ) O(n) O(n)
    // 带头节点按位序删除
    bool listDelete_h(linkList &L, int i, int &e) {
          
          
        if (i < 1)
            return false;
        LNode* p = L;
        int j = 0;
        while (p!=NULL && j<i-1) {
          
            // 将p指到第i-1个节点
            p = p->next;
            j++;
        }
        if (p == NULL || p->next == NULL)   // 表示第i-1或第i个节点不存在
            return false;
        LNode *q = p->next;
        e = q->data;
        p->next = q->next;
        free(q);
        return true;
    }
    
  2. 指定结点删除: 时间复杂度 O ( 1 ) O(1) O(1) 版本 (偷天换日法)
    bool deleteNode(LNode *p) {
          
          
        if (p == NULL || p->next == NULL)   // 这种方法存在缺陷
            // 当删除的结点位最后一个结点时,则只能从表头结点开始寻找p的前驱
            return false;
        LNode *q = p->next;
        p->data = q->data;
        p->next = q->next;
        free(q);
        return true;
    }
    

d. 查找

  1. 带头结点的按位查找: 平均时间复杂度 O ( n ) O(n) O(n)
    LNode * getElem(linkList L, int i) {
          
          
        if (i < 1)
            return NULL;
        LNode *p = L;
        int j = 0;
        while (p!=NULL && j<i) {
          
          
            p = p->next;
            j++;
        }
        return p;
    }
    
  2. 带头结点的按值查找: 平均时间复杂度 O ( n ) O(n) O(n)
    LNode * locateElem(linkList L, int e) {
          
          
        LNode *p = L->next;
        while (p != NULL && p->data != e)
            p = p->next;
        return p;
    }
    
  3. 带头节点的求表长: 平均时间复杂度 O ( n ) O(n) O(n)
    int listLen(linkList L) {
          
          
        int len = 0;
        LNode *p = L;
        while (p->next != NULL) {
          
          
            p = p->next;
            len++;
        }
        return len;
    }
    

e. 建立

  1. 带头结点的尾插法建立单链表:
    // 带头结点的尾插法建立单链表
    linkList tailBuildLink_h(linkList &L) {
          
          
        int x;
        L = (linkList)malloc(sizeof(LNode));
        LNode *s, *r = L;
        scanf("%d", &x);
        while (x != 999) {
          
          
            s = (LNode *)malloc(sizeof(LNode));
            s->data = x;
            r->next = s;
            r = s;
            scanf("%d", &x);
        }
        r->next = NULL;
        return L;
    }
    
  2. 带尾结点的尾插法建立单链表:
    linkList headBuildLink_h(linkList &L) {
          
          
        int x;
        L = (linkList)malloc(sizeof(LNode));
        L->next = NULL;
        LNode *s;
        scanf("%d", &x);
        while (x != 999) {
          
          
            s = (LNode *)malloc(sizeof(LNode));
            s->data = x;
            s->next = L->next;
            L->next = s;
            scanf("%d", &x);
        }
        return L;
    }
    

IV. 双链表

  1. 定义结构体:
    typedef struct dNode {
          
          
        int data;
        struct dNode *prior, *next;
    }dNode, *dLinkList;
    
  2. 初始化双链表:
    bool initDLink(dLinkList &L) {
          
          
        L = (dLinkList)malloc(sizeof(dNode));
        if (L == NULL)
            return false;
        L->prior = NULL;
        L->next = NULL;
        return true;
    }
    
  3. 判空:
    bool emptyDLink(dLinkList &L) {
          
          
        return L->next == NULL;
    }
    
  4. 指定结点后插结点:
    bool insertNextNode(dNode *p, dNode *s) {
          
          
        if (p == NULL || s == NULL)
            return false;
        s->prior = p;
        s->next = p->next;
        if (p->next != NULL)
            p->next->prior = s;
        p->next = s;
        return true;
    }
    
  5. 删除指定结点后一个结点:
    bool deleteNextNode(dNode *p) {
          
          
        if (p == NULL || p->next == NULL)
            return false;
        dNode *q = p->next;
        if (q->next != NULL)
            q->next->prior = p;
        p->next = q->next;
        free(q);
        return true;
    }
    
  6. 销毁链表:
    void destroyList(dLinkList &L) {
          
          
        while (L->next != NULL)
            deleteNextNode(L);
        free(L);
        L = NULL;
    }
    

V. 循环链表

a. 循环单链表

循环单链表: 表尾结点的 n e x t next next 指针指向头节点
对比单链表: 从一个结点出发可以找到其他任何一个结点
在这里插入图片描述

  1. 循环单链表结构定义:
    typedef struct clNode {
          
          
        int data;
        struct clNode *next;
    }clNode, *clList;
    
  2. 循环单链表初始化:
    bool initCList(clList &L) {
          
          
        L = (clNode *)malloc(sizeof(clNode));
        if (L == NULL)
            return false;
        L->next = L;
        return true;
    }
    
  3. 循环单链表判空操作:
    bool emptyCList(clList L) {
          
          
        return L->next == L;
    }
    
  4. 判断结点p是否为尾结点:
    bool isTail(clList L, clNode *p) {
          
          
        return p->next == L;
    }
    

b. 循环双链表

循环双链表: 在双链表的基础上, 表头结点的 p r i o r prior prior 指向尾结点, 表尾结点的 n e x t next next 指向头节点

  1. 结构定义:
    typedef struct cdNode {
          
          
        int data;
        struct cdNode *prior, *next;
    }cdNode, *cdLinkList;
    
  2. 初始化:
    bool initCDLinkList(cdLinkList &L) {
          
          
        L = (cdNode *)malloc(sizeof(cdNode));
        if (L == NULL)
            return false;
        L->prior = L;
        L->next = L;
        return true;
    }
    
  3. 判空 / 判尾:
    bool isEmpty(cdLinkList L) {
          
          
        return L->next == L;
    }
    bool isTail(cdLinkList L, cdNode *p) {
          
          
        return p->next == L;
    }
    
  4. 在p结点后插入s结点:
    bool insertNextNode(cdNode *p, cdNode *s) {
          
          
        if (p == NULL || s == NULL)
            return false;
        s->next = p->next;
        s->prior = p;
        p->next->prior = s;
        p->next = s;
        return true;
    }
    
  5. 删除p结点的后继结点:
    bool deleteNextNode(cdNode *p, cdNode *q) {
          
          
        if (p == NULL || q == NULL)
            return false;
        p->next = q->next;
        q->next->prior = p;
        free(q);
        return true;
    }
    

VI. 静态链表

静态链表: 分配一整片连续的内存空间, 各个结点集中安置; 其中每个结点包含两部分: 数据和指向下一个结点的 “指针”
在这里插入图片描述

  1. 定义结构体:
#define maxSize 10
struct Node {
    
    
	int data;
	int next;
};
typedef struct Node sLinkList[maxSize];
  1. 简述基本操作实现:
    1. 查找: 从头节点出发依次往后遍历
    2. 插入位序为 i i i 的结点:
      新建空结点存入数据;
      从头节点找到第 i − 1 i-1 i1 个结点;
      将新结点的 n e x t next next 修改为第 i − 1 i-1 i1 n e x t next next
      将第 i − 1 i-1 i1 结点的 n e x t next next 修改为新结点的位置
    3. 删除位序为 i i i 的结点:
      从头结点出发找到第 i − 1 i-1 i1 个结点
      修改第 i − 1 i-1 i1 个结点的 n e x t next next 指针
      将第 i i i 个结点的 n e x t next next 指针设为 -2

VII. 顺序表和链表的比较

在这里插入图片描述

顺序表 链表
创建 需预分配空间,且不方便扩展 只需分配一个头结点,且方便扩展
销毁 (静态数组)系统自动回收空间(动态数组)需要手动free 每个结点依次free
增删 需要移动大量元素 O ( n ) O(n) O(n) 只需修改指针, 但查找也需 O ( n ) O(n) O(n)
查找 (按位查找) O ( 1 ) O(1) O(1);(按值查找) O ( log ⁡ 2 n ) O(\log2n) O(log2n) 都需要 O ( n ) O(n) O(n)

猜你喜欢

转载自blog.csdn.net/JackyAce6880/article/details/126064286