线性表的基本概念及存取实现

本文详细讲解了线性表的顺序存储和链式存储方式,涉及基本操作如查找、插入、删除、更新,以及合并有序表的高效算法。重点介绍了顺序表的O(n)复杂度和链表的O(n)查找插入。还探讨了不同表结构的空间效率,以及代码实例演示。
摘要由CSDN通过智能技术生成

内容

•线性表:顺序实现和链式实现

•线性表基本操作

•线性表合并操作

•代码实现

1.线性表的顺序存储和链式存储

线性表:是一种逻辑结构,元素之间成一对一的关系

存储结构:分为顺序存储和链式存储

*key1:线性表是指各数据元素间保持‘1对1’关系的数据结构

*key2:线性表分为顺序表(数组)和链表,分别通过元素相邻和保存指针域的方式来实现'1对1'

(1)分析:因为第一个元素的下标为a0所以第五个元素的下标为4,所以第五个元素的地址为100+2(n-1)

(2)略

(3)分析:读取第i个元素只需要通过计算一步就可以完成,所以为o(1)

(4)分析:指针也是占位置的,在32位的电脑中占4个字节,在64位的电脑中占8个字节。又因为存储密度=单链表数据项所占空间/结点所占空间,
结点所占空间由数据项所占空间和存放后继结点地址的链域,所以,存储密度小于1

2:线性表的基本操作

 (1)查找元素:按值查找

•给定长度为n的线性表,查找值为v的元素

•(最坏)从头到尾遍历=>时间复杂度O(n)

(2)顺序表新增/删除元素

•给定长度为n的顺序表,在指定位置i插入一个新元素(插入位置的元素全部向后移动一位)

•给定长度为n的顺序表,删除位置i的元素(删除位置后面的元素全部向后移动一格)

•需要将位置i到位置n-1的所有元素都向后或向前移动一格

•在最坏情况下(i=0)下,需要移动全部的n个元素=>时间复杂度为O(n)

•无需利用额外空间=>空间复杂度为O(1)

(3)(单)链表新增元素

•给定长度为n的顺序表,在第i个结点插入一个新元素,插入完成后新元素是链表的第i个结点。

•给定长度为n的顺序表,在第i个结点插入一个新元素

•首先需要从头结点开始逐个向后找i-1次=>时间复杂度为O(n)

•找到后插入只需要修改第i-1个结点和待插入结点的【后继结点地址】即可=>O(1)

*先让新增结点指向本来的第i个元素,再让其指向它的前驱结点,然而顺序不能相反,因为指向本来的第i个元素是为了保存它的后继结点的地址,如果不先指向的话就找不到了。

•无需利用额外空间=>空间复杂度为O(1)

(4)(单)链表删除元素

•给定长度为n的单链表,删除第i个结点

•需要移动到第i个结点的前驱结点,最坏情况下移动n-1次=>时间复杂度为O(n)

•修改前驱结点的后继指针=>O(1)

•无需利用额外空间=>空间复杂度为O(1)

(5)顺序表更新元素

•给定长度为n的顺序表,更新位置i的元素

•无论i的值如何,都可以通过i直接访问位置i元素,将其更新为V'=>时间复杂度为O(1)=>随机存取

(6)(单)链表更新元素

•给定长度为n的顺序表,更新第i个结点的值

•需要从头结点开始按顺序找到第i个结点才能访问并更新它=>随机存取

•最坏情况遍历整个链表=>时间复杂度为O(n)

*key1:顺序表(数组)中增加和删除一个元素将导致该位置后的元素前移或后移,复杂度为O(n)

*key2:单链表增加和删除元素虽然不用移动元素,但需先找到前驱结点,复杂度为O(n) 

    D.线性表的顺序存储实现优于链式存储结构

3.线性表合并操作

 

•设A表长度为n,B表长度为m

•对于B表中的每个元素,都需要先判断其是否已经存在A里=>(mn)

•如果存在,无需插入,如果不存在,将其插入在A的末尾=>O(1)

•总时间复杂度为O(mn)

•空间复杂度呢?

        •顺序表:O(m+n)*先开辟空间再进行存储

        •链表:O(1)*改改指针就可以改变存储空间

 •设A表长度为n,B表长度为m

•先预留结果表空间:n+m个元素

•从表头开始同时逐个访问A和B表元素,将当前位置上较小者放入结果表并后移一位

•总时间复杂度为O(m+n)

•空间复杂度为O(m+n)

•设A表长度为n,B表长度为m

•先创建一个头结点(哑结点dummy),其数据没有实际意义,只为用它的【指针域】

•从表头开始逐个同时遍历A和B,将当前已完成合并的表尾元素的后继结点设置为当前A和B游标中较小的一个,并将该游标向后移动一位

•时间复杂度为O(m+n)

•空间复杂度为O(1)*因为它原地修改了数组元素的指针域,是无需额外空间的

*key1:合并两个有序表:逐一比较两表当前元素,将正确的元素添加进结果表并移动游标

 4.代码实现

/*
线性表
*/

#include <algorithm>  // for std::min
#include <cstdio>

/* 顺序表 */
// 顺序表数据结构
#define MAX_SIZE 1000
typedef struct List {
    int *elem;
    int length;
} List;

// 初始化顺序表
List Create() {
    List L;
    L.elem = new int[MAX_SIZE];
    L.length = 0;
    return L;
}

// 顺序表插入
void ListInsert(List &L, int i, int val) {
    if ((i < 0) || (i > L.length))
        return;
    if (L.length == MAX_SIZE)
        return;
    for (int j = L.length; j >= i + 1; j--) L.elem[j] = L.elem[j - 1];
    L.elem[i] = val;
    ++L.length;
}

// 顺序表删除
void ListDelete(List &L, int i) {
    if ((i < 0) || (i > L.length - 1))
        return;
    for (int j = i; j <= L.length - 2; j++) L.elem[j] = L.elem[j + 1];
    --L.length;
}

/* 单链表 */
// 单链表数据元素:学生
typedef struct {
    char id[10];
    int age;
    double score;
} Student;

// 单链表数据结构
typedef struct LinkListNode {
    Student student;
    struct LinkListNode *next;  // 指针域
} Node;

// 单链表新增, 返回值为插入后的链表首元素指针
Node *LinkListInsert(Node *head, int i, Student &s) {
    // head为NULL或i为0意味着需要在链表头前面插入
    // 直接使用s和原head创建该结点即可, 返回该结点指针
    if (!head || i == 0) {
        return new Node{s, head};
    }

    // i不为0时, 先顺链表向后移动i-1位, 找到i的前驱结点
    Node *p = head;
    int j = 0;
    while (p && j < i - 1) {
        p = p->next;
        ++j;
    }

    if (!p) {  // !p说明链表没有i-1位置元素, 即i超出可插入下标
        return head;
    }

    Node *newNode = new Node;  // 创建新结点
    newNode->student = s;      // 设置结点数据域

    // 以下两步的顺序不能反!否则丢失了p->next,再也找不回来了!
    newNode->next = p->next;  // 设置指针域
    p->next = newNode;        // 完成插入

    return head;
}

// 单链表删除
Node *LinkListDelete(Node *head, int i) {
    // 删除原本的链表首元素
    if (!head || i == 0) {
        Node *newHead = head->next;
        if (head) {
            delete (head);
        }
        return newHead;
    }

    // 删除其他元素: 先找到前驱结点
    Node *p = head;
    int j = 0;
    while (p && j < i - 1) {
        p = p->next;
        ++j;
    }

    // !p说明链表没有i-1位置元素, !p->next说明没有i位置元素,
    // 这里利用了||的短路效应, 顺序不可调换
    if (!p || !p->next) {
        return head;
    }

    Node *n = p->next;
    p->next = n->next;
    delete (n);
    return head;
}

/* 合并两个有序顺序表 */
void MergeTwoArray(List A, List B, List &C) {
    C.elem = new int(A.length + B.length);
    int ia = 0, ib = 0, ic = 0;
    while (ia < A.length && ib < B.length) {
        if (A.elem[ia] < B.elem[ib]) {
            C.elem[ic++] = A.elem[ia++];  // 添加元素 + 移动游标
        } else {
            C.elem[ic++] = B.elem[ib++];
        }
    }
    while (ia < A.length) {
        C.elem[ic++] = A.elem[ia++];
    }
    while (ib < B.length) {
        C.elem[ic++] = B.elem[ib++];
    }
    C.length = A.length + B.length;
}

/* 合并两个有序链表 */
Node *MergeTwoLinkList(Node *headA, Node *headB) {
    Node *dummy = new Node;  // 头结点
    dummy->next = NULL;      // 不关心头结点的值,只利用其指针域

    Node *pa = headA, *pb = headB, *pc = dummy;
    while (pa && pb) {
        if (pa->student.age < pb->student.age) {
            pc->next = pa;  // 添加元素
            pa = pa->next;  // 移动游标
        } else {
            pc->next = pb;
            pb = pb->next;
        }
        pc = pc->next;  // 移动结果表的游标
    }
    if (pa) {
        pc->next = pa;
    } else {
        pc->next = pb;
    }

    Node *res = dummy->next;
    delete (dummy);
    return res;
}

int main() {
    Student students[] = {
        {"001", 12, 78.5f},
        {"002", 13, 80.f},
        {"003", 15, 98.5f},
    };

    // 创建一个空链表
    Node *list = NULL;

    // 插入一些学生数据元素
    for (int i = 0; i < 3; ++i) {
        list = LinkListInsert(list, 0, students[i]);
    }

    // 遍历链表打出来看看
    for (Node *p = list; p; p = p->next) {
        printf("id:%s, age:%d, score:%.1f\n", p->student.id, p->student.age,
               p->student.score);
    }
    printf("----------\n");

    // 删除位于下标1位置(第2个)的元素再打印看看
    list = LinkListDelete(list, 1);
    for (Node *p = list; p; p = p->next) {
        printf("id:%s, age:%d, score:%.1f\n", p->student.id, p->student.age,
               p->student.score);
    }
}

 练习:

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阳光少年.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值