内容
•线性表:顺序实现和链式实现
•线性表基本操作
•线性表合并操作
•代码实现
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);
}
}
练习: