目录
顺序表
本书中的顺序表,是适合对于文本进行操作,也就是结合文件的操作,对于程序题来说,我选择了其中的几个功能函数,并且相对于进行了变形,也就编写除了几个题目功能模块模板来供大家参考和使用,帮助大家更好的理解顺序表的原理以及操作
线性表的创建我们可以使用c当中的malloc和free来动态创建,也可以用c ++ 当中的new和delete来动态创建,由于本人在大一上学期学的是c语言,大一下学期学习的是c++,因此我们主要挑选c和c++当中比较方便使用和方便理解的方法,因为每个程序的情况不同,所以我们要对于不同的情况进行抽象化处理,也就是c++中的抽象化思维,也就是面向对象的程序设计,但是与自定义类所不同的是,自定义类中的数据由于不进行类型限定的话默认为pirvate类型数据,而private类型数据的访问必须设置本类的函数,此方法过于麻烦,因此我们不用class,改为使用struct。我们将顺序表当中每个元素的种类设置为TypeElem,读者们可以自行代入处理
顺序表的存储结构
typedef struct
{
ElemType *elem;
int length;
}SqList;
语言版本
纯c ++ 版本点击此处
纯c版本如下所示
准备工作
头文件库函数宏定义
#include <stdio.h>//头文件
#include <malloc.h>//malloc与free函数所在库
#include <string.h>//strcmp函数所在库
#include <stdbool.h>//bool所在库
#define INITSIZE 5//初始顺序表大小
#define EXTRASIZE 5//自动扩容是增加的顺序表大小
ElemType与SqList定义
在这里我们将以图书管理系统作为示范模板,每本图书都有其独特的编号,姓名以及价格
typedef struct
{
int pos;//用于记录图书的初始位置
char num[1000];
char name[1000];
double price;
}BOOK;
typedef struct
{
BOOK *elem;
int length;
int size;
}SqList;
动态分配内存
//测试通过,初始化顺序表
void InitSqList(SqList *L)
{
L->elem = (BOOK*)malloc(sizeof(BOOK)*INITSIZE);//动态分配初始空间
if(L->elem == NULL)
{
printf("创建失败\n");
return;
}
L->size = INITSIZE;//更新顺序表容量大小
L->length = 0;//初始化顺序表长度
}
内存动态扩增
当内存不够的情况下我们选择动态扩增内存
//测试通过,内存扩增
bool AddSpace(SqList *L)
{
BOOK *newelem = (BOOK*)realloc(L->elem,sizeof(BOOK)*((L->size) + EXTRASIZE));//重新扩增内存
if(newelem == NULL)
{
printf("申请分配内存空间失败,内存空间不足\n");
return false;
}
L->elem = newelem;//更新扩增后的elem
L->size += EXTRASIZE;//更新扩增后的顺序表大小
return true;
}
插入
头插法
//测试通过,头插法
void push_front(SqList *L,BOOK e)
{
if(L->length >= L->size && !AddSpace(L))//当顺序表已满且无法扩增时
{
printf("顺序表空间已满,无法在尾部插入数据\n");
return;
}
for(int i = L->length;i > 0;i -- )//将所有元素后移一位
{
L->elem[i] = L->elem[i - 1];
}
L->elem[0] = e;//插入元素
L->length ++ ;//顺序表长度加一
}
尾插法
//测试通过,尾插法
void push_back(SqList *L,BOOK e)
{
if(L->length >= L->size && !AddSpace(L))//当顺序表已满且无法扩增时
{
printf("顺序表空间已满,无法在尾部插入数据\n");
return;
}
L->elem[L->length] = e;//插入元素
L->length ++ ;//顺序表长度加一
}
指定位置插入
//测试通过,指定位置idx插入
void Insert_pos(SqList *L,int idx,BOOK e)
{
if(idx < 1 || idx > L->length + 1)
{
printf("插入数据位置非法无法插入数据\n");
return;
}
if(L->length >= L->size && !AddSpace(L))//当顺序表已满且无法扩增时
{
printf("顺序表的空间已满,不可以插入\n");
}
for(int i = L->length;i >= idx;i -- )//将第i - L->length个元素全部后移一位
{
L->elem[i] = L->elem[i - 1];
}
L->elem[idx - 1] = e;//插入元素
L->length ++ ;//顺序表长度加一
}
删除
删头
//测试通过,删头
void pop_front(SqList *L)
{
if(L->size == 0)
{
printf("顺序表已空,不能删除尾部数据\n");
return;
}
for(int i = 0;i < L->length - 1;i ++ )//将第2 - L->length个元素整体前移覆盖
{
L->elem[i] = L->elem[i + 1];
}
L->length -- ;//顺序表长度减一
}
删尾
//测试通过,删尾
void pop_back(SqList *L)
{
if(L->size == 0)
{
printf("顺序表已空,不能删除尾部数据\n");
return;
}
L->length -- ;//顺序表长度减一即可
}
删指定位置
//测试正确,删除指定位置元素
void Delete_pos(SqList *L,int idx)
{
if(idx < 1 || idx > L->length + 1)
{
printf("删除数据位置非法无法删除数据\n");
return;
}
for(int i = idx - 1;i < L->length - 1;i ++ )//覆盖删
{
L->elem[i] = L->elem[i + 1];
}
L->length -- ;//顺序表长度减一
}
删指定key值
前置函数
//测试正确,在此key值为num即图书序号
int find(SqList *list,char key[])
{
//遍历顺序表,查找数据
for(int i=0; i<list->length; ++i)
{
if(strcmp(list->elem[i].num,key) == 0)return i + 1; //找到数据返回
}
return -1;
}
//测试正确,删除指定位置元素
void Delete_pos(SqList *L,int idx)
{
if(idx < 1 || idx > L->length + 1)
{
printf("删除数据位置非法无法删除数据\n");
return;
}
for(int i = idx - 1;i < L->length - 1;i ++ )//覆盖删
{
L->elem[i] = L->elem[i + 1];
}
L->length -- ;//顺序表长度减一
}
功能函数
//测试正确,删除指定key值元素
void Delete_val(SqList *L,char key[])
{
int pos = find(L,key);//利用前置find函数
if(pos == -1)
{
printf("元素不存在\n");
return;
}
Delete_pos(L,pos);
}
基本操作
取表长
//测试正确,获取顺序表长
int length(SqList *L)
{
return L->length;
}
排序
前置函数
//测试正确,升序依据
int cmp1(BOOK a,BOOK b)
{
if(a.price != b.price)return a.price < b.price;
else return a.pos < b.pos;
}
//测试正确,降序依据
int cmp2(BOOK a,BOOK b)
{
if(a.price != b.price)return a.price > b.price;
else return a.pos < b.pos;
}
功能函数
//测试正确,冒泡升序cmp1(降序cmp2)排序
void sort(SqList *L)
{
for(int i = 0;i < L->length;i ++ )
{
for(int j = 0;j < L->length;j ++ )
{
if(cmp2(L->elem[i],L->elem[j]) > 0)
{
BOOK t;
t = L->elem[i];
L->elem[i] = L->elem[j];
L->elem[j] = t;
}
}
}
}
去重
前置函数
//测试正确,升序依据
int cmp1(BOOK a,BOOK b)
{
if(a.price != b.price)return a.price < b.price;
else return a.pos < b.pos;
}
//测试正确,降序依据
int cmp2(BOOK a,BOOK b)
{
if(a.price != b.price)return a.price > b.price;
else return a.pos < b.pos;
}
功能函数
//测试通过,去重(Delete_pos为前置条件)
void unique(SqList *L)
{
for(int i = 0;i < L->length - 1;i ++ )
{
for(int j = i + 1;j < L->length;j ++ )
{
if(L->elem[i].price == L->elem[j].price && strcmp(L->elem[i].num,L->elem[j].num) == 0 && strcmp(L->elem[i].name,L->elem[j].name) == 0)
{
Delete_pos(L,j);
}
}
}
}
反转
void reverse(SqList *L)
{
//利用双指针算法实现
for(int i = 0,j = L->length - 1;i < j;i ++ ,j -- )
{
BOOK t;
t = L->elem[i];
L->elem[i] = L->elem[j];
L->elem[j] = t;
}
}
顺序表合并
前置函数
//测试正确,升序依据
int cmp1(BOOK a,BOOK b)
{
if(a.price != b.price)return a.price < b.price;
else return a.pos < b.pos;
}
//测试正确,降序依据
int cmp2(BOOK a,BOOK b)
{
if(a.price != b.price)return a.price > b.price;
else return a.pos < b.pos;
}
功能函数
//测试通过,二路归并升序cmp1(降序cmp2)合并顺序表
void merge(SqList *L,SqList *L1,SqList *L2)
{
L->size = L1->size + L2->size;//新表大小为二表之和
L->elem = (BOOK*)malloc(sizeof(BOOK)*L->size);//动态分配内存空间
if(L->elem == NULL)
{
printf("顺序表合并失败\n");
return;
}
//标准的二路归并排序,结合cmp函数
int i = 0,j = 0,k = 0;
while(i < L1->length && j < L2->length)
{
if(cmp1(L1->elem[i],L->elem[j]) > 0)L->elem[k ++ ] = L1->elem[i ++ ];
else L->elem[k ++ ] = L2->elem[j ++ ];
}
while(i < L1->length)L->elem[k ++ ] = L1->elem[i ++ ];
while(j < L2->length)L->elem[k ++ ] = L2->elem[j ++ ];
L->length = L1->length + L2->length;
}
输出
//测试通过,遍历并输出顺序表
void ShowSqList(SqList *L)
{
for(int i = 0;i < L->length;i ++ )
{
printf("%s %s %.2lf\n",L->elem[i].num,L->elem[i].name,L->elem[i].price);
}
}
销毁
void Destory(SqList *L)
{
free(L->elem);//释放内存空间
L->elem = NULL;//指针置空
L->size =0;//顺序表大小置0
L->length = 0;//顺序表长度置0
}
本顺序表是按照图书管理系统为背景下创作的,即ElemType为BOOK,由于对于不同的情况ElemType中的变量类型和数量并不相同,本顺序表只是为大家提供一个参考类型,具体可自由发挥
练习题集
链表
使用数据结构当中的链式存储结构来实现线性表的创建以及多项功能等,链式存储结构不必像顺序存储结构那样开辟指定的空间大小,即使是像本文章中的顺序表增加的动态扩容功能,在处理规模较大且未知元素个数时还是最好使用链表作为存储结构,链表的存储密度不高,存储空间不能够经济的使用,在对于取值的时候时间复杂度较高,但是删除的时候时间复杂度较小
链表的存储结构
typedef struct LNode
{
ElemType data;
strcut LNode *next;
}LNode,*LinkList;
头文件函数以及全局变量的使用
#include <iostream>
#include <iomanip>
#include <vector>
#include <algorithm>
using namespace std;
int length = 0;
存储类型的引入以及定义
在这里我们以图书来作为ElemType
typedef struct
{
int pos;
string num;
string name;
double price;
}BOOK;
typedef struct LNode
{
BOOK data;
struct LNode *next;
}*LinkList;
功能性函数
初始化
//测试正确,创建单链表的初始化,创建空表
void InitList(LinkList &L)
{
L = new LNode;
L -> next = NULL;
}
取值
//测试正确,取单链表当中第i个元素的值
void GetElem(LinkList L,int i,BOOK &e)
{
LNode *p = L -> next;
int j = 1;
while(p && j < i)
{
p = p -> next;
j ++ ;
}
if((!p)||(j > i))
{
printf("第%d个元素不存在于单链表当中\n",i);
return;
}
e = p -> data;
}
查询
//测试成功,查询key值为e的地址
LNode* LocateElem(LinkList L,string name)
{
LNode *p = L -> next;
while((p)&&(!(p->data.name == name)))p = p -> next;
return p;
}
插入
//测试成功,将元素e插入链表使其成为第i个元素
void ListInsert(LinkList &L,int i,BOOK e)
{
LNode *p = L;
int j = 0;
while((p) && (j < i - 1))
{
p = p -> next;
j ++ ;
}
if((!p) || (j > i - 1))
{
printf("插入位置非法\n");
return;
}
LNode *s = new LNode;
s -> data = e;
s -> next = p -> next;
p -> next = s;
}
删除
//测试正确,删除单链表中第i个节点
void LinkListDelete(LinkList &L,int i)
{
LNode *p = L;
int j = 0;
while((p ->next) && (j < i - 1))
{
p = p -> next;
j ++ ;
}
if(!(p->next) || (j > i - 1))
{
printf("删除位置非法\n");
return;
}
LNode *q = p -> next;
p -> next = q -> next;
delete q;
}
头插法创建
//测试通过,头插法创建长度为n的单链表
void Insert_H(LinkList &L,int n)
{
InitList(L);
while(n -- )
{
LNode *p = new LNode;
cin >> p->data.num >> p->data.name >> p->data.price;
p -> next = L -> next;
L -> next = p;
}
}
尾插法创建
//测试通过,尾插法创建长度为n的单链表
void Insert_T(LinkList &L,int n)
{
InitList(L);
LNode *r = L;
while(n -- )
{
LNode *p = new LNode;
cin >> p->data.num >> p->data.name >> p->data.price;
p -> next = NULL;
r -> next = p;
r = p;
}
}
遍历
//测试通过,对于单链表当中元素的遍历
void ShowLinkList(LinkList &L)
{
LNode *p = L -> next;
if(p == NULL)
{
printf("该链表为空链表\n");
return;
}
while(p)
{
cout << p->data.num << ' ' << p->data.name << ' ' << setiosflags(ios::fixed) << setprecision(2) << p->data.price << endl;
p = p -> next;
}
}
排序
//按照价格高,原始序次低排序
int cmp(BOOK &A,BOOK &B)
{
if(A.price != B.price)return A.price > B.price;
else if(A.pos != B.pos) return A.pos < B.pos;
}
void SortLinkList(LinkList &L)
{
vector<BOOK> A;
LNode *p = L->next;
while(p)
{
A.push_back(p->data);
p = p -> next;
}
sort(A.begin(),A.end(),cmp);
Destory(L);
InitList(L);
for(int i = 0;i < A.size();i ++ )
{
ListInsert(L,i + 1,A[i]);
}
}
去重
void UniqueLinkList(LinkList &L)
{
vector<BOOK> A;
LNode *p = L->next;
while(p)
{
A.push_back(p->data);
p = p -> next;
}
vector<BOOK> B;
for(int i = 0;i < A.size();i ++ )
{
int flag = 1;
for(int j = i + 1;j < A.size();j ++ )
{
if((A[i].num == A[j].num) && (A[i].name == A[i].name) && (A[i].price == A[i].price))
{
flag = 0;
break;
}
}
if(flag == 1)B.push_back(A[i]);
else if(flag == 0)continue;
}
length = B.size();
Destory(L);
InitList(L);
for(int i = 0;i < B.size();i ++ )
{
ListInsert(L,i + 1,B[i]);
}
}
销毁
//测试正确,对于单链表的销毁
void Destory(LinkList &L)
{
LNode *p = L;
while(L -> next)
{
p = L -> next;
L -> next = p -> next;
delete p;
}
}
练习题集
单链表
单链表原理
单链表链式存储结构的特点是:用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。因此,为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对于数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示其直接后记的信息(直接后继的存储位置),这两部分信息组成数据元素ai的存储映翔,称为节点node,它包含两个区域,:存储数据元素信息的域被称为数据域,存储直接后继存储位置的域称为指针域。指针域当中存储的信息称为指针或链。n个节点链接称为一个链表,即为线性表的链式存储结构,又因每个节点当中仅仅包含一个指针域,所以又称为线性链表或者单链表
在单链表之中,头指针指向单链表的第一个节点,尾指针指向NULL(空),若链表为空,则头指针指向的是NULL
由单链表的存储结构可得,LNode *与LinkList是等价的,实际操作时我们通常利用LinkList来定义单链表的头指针,LNode来定义指向任意结点的指针变量,由于单链表是由表头指针唯一确定的,所以单链表可以用头指针的名字来命名,若头指针名为L,则单链表名字为L,LinkList p为节点地址,LNode *p为节点变量
单链表的基本操作
初始化
//测试正确,创建单链表的初始化,创建空表
void InitList(LinkList &L)
{
L = new LNode;
L -> next = NULL;
}
头插创建
//测试通过,头插法创建长度为n的单链表
void Insert_H(LinkList &L,int n)
{
InitList(L);
while(n -- )
{
LNode *p = new LNode;
scanf("%d",&p -> data);
p -> next = L -> next;
L -> next = p;
}
}
尾插创建
//测试通过,尾插法创建长度为n的单链表
void Insert_T(LinkList &L,int n)
{
InitList(L);
LNode *r = L;
while(n -- )
{
LNode *p = new LNode;
scanf("%d",&p -> data);
p -> next = NULL;
r -> next = p;
r = p;
}
}
取值
//测试正确,取单链表当中第i个元素的值
void GetElem(LinkList L,int i,int &e)
{
LNode *p = L -> next;
int j = 1;
while(p && j < i)
{
p = p -> next;
j ++ ;
}
if((!p)||(j > i))
{
printf("第%d个元素不存在于单链表当中\n",i);
return;
}
e = p -> data;
}
查询
//测试成功,查询key值为e的地址
LNode *LocateElem(LinkList L,int e)
{
LNode *p = L -> next;
while(p&&p->data != e)p = p -> next;
return p;
}
插入(head)
void InsertLinkList_H(LinkList &L,int idx,int e)
{
if(L == NULL || L -> next == NULL)return;
LNode *p = L;
int i = 0;
while(p && i < idx - 1)
{
p = p -> next;
i ++ ;
}
if(!p || i > idx - 1)return;
LNode *s = new LNode;
s -> data = e;
s -> next = p -> next;
p ->next = s;
}
插入(Tail)
void InsertLinkList_T(LinkList &L,int idx,int e)
{
if(L == NULL || L -> next == NULL)return;
LNode *p = L;
int i = 0;
while(p && i < idx)
{
p = p -> next;
i ++ ;
}
if(!p || i > idx)return;
LNode *s = new LNode;
s -> data = e;
s -> next = p -> next;
p ->next = s;
}
删除
//测试正确,删除单链表中第i个节点
void LinkListDelete(LinkList &L,int i)
{
LNode *p = L;
int j = 0;
while((p ->next) && (j < i - 1))
{
p = p -> next;
j ++ ;
}
if(!(p->next) || (j > i - 1))
{
printf("删除位置非法\n");
return;
}
LNode *q = p -> next;
p -> next = q -> next;
delete q;
}
遍历
//测试通过,对于单链表当中元素的遍历
void ShowLinkList(LinkList &L)
{
LNode *p = L -> next;
if(p == NULL)
{
printf("该链表为空链表\n");
return;
}
while(p)
{
printf("%d ",p -> data);
p = p -> next;
}
}
销毁
//测试正确,对于单链表的销毁
void Destory(LinkList &L)
{
LNode *p = L;
while(L -> next)
{
p = L -> next;
L -> next = p -> next;
delete p;
}
}
排序
void sort(LinkList &L)
{
//创建三个子单链表,A存储正数,B存储0,C存储负数
LinkList A,B,C;
//A子单链表初始化
A = new LNode;
A -> next = NULL;
//B子单链表初始化
B = new LNode;
B -> next = NULL;
//C子单链表初始化
C = new LNode;
C -> next = NULL;
//设置指针p用于遍历原单链表
LNode *p = L->next;
//设置ABC的尾指针
LNode *r1 = A;
LNode *r2 = B;
LNode *r3 = C;
//通过对于原单链表一遍的遍历拆分出三个子单链表
while(p)
{
//拆分负数
if(p->data < 0)
{
LNode *t = new LNode;
t -> data = p -> data;
t -> next = NULL;
r1 -> next = t;
r1 = t;
}
//拆分0
else if(p->data == 0)
{
LNode *t = new LNode;
t -> data = p -> data;
t -> next = NULL;
r2 -> next = t;
r2 = t;
}
//拆分正数
else if(p->data > 0)
{
LNode *t = new LNode;
t -> data = p -> data;
t -> next = NULL;
r3 -> next = t;
r3 = t;
}
p = p->next;
}
//对于子链表进行合并,为了避免出现某个子链表空表导致合并时出现断链的情况,使用遍历进行合并操作而不是对于原链表和三个子链表的头尾指针的指向进行操作
LNode *r = L;
//合并A链表中的内容
p = A->next;
while(p)
{
LNode *t = new LNode;
t -> data = p -> data;
t -> next = NULL;
r -> next = t;
r = t;
p = p -> next;
}
//合并B链表中的内容
p = B->next;
while(p)
{
LNode *t = new LNode;
t -> data = p -> data;
t -> next = NULL;
r -> next = t;
r = t;
p = p -> next;
}
//合并C链表中的内容
p = C->next;
while(p)
{
LNode *t = new LNode;
t -> data = p -> data;
t -> next = NULL;
r -> next = t;
r = t;
p = p -> next;
}
//对于尾指针进行处理
r->next = NULL;
}
查找最大值的节点
LNode* search(LinkList &L)
{
LNode *p = L;
LNode *q;
if(p->next == NULL)
{
printf("此链表为空链表,查找失败\n");
return NULL;
}
else
{
p = L -> next;
q = p;
}
while(p)
{
if(p->data > q->data)q = p;
p = p -> next;
}
return q;
}
逆转
void ReverseLinkList(LinkList &L)
{
LNode *pre,*cur,*next;
cur = L -> next;
pre = NULL;
if(L == NULL || L->next == NULL)return;
while(cur)
{
next = cur -> next;
cur -> next = pre;
pre = cur;
cur = next;
}
L->next = pre;
}
删除指定区间
void Delete(LNode *L,int mink,int maxk)
{
LNode *cur = L->next;
LNode *pre = L;
if(cur == NULL || pre == NULL)return;
while(cur)
{
if(cur -> data >= mink && cur -> data <= maxk)
{
pre -> next = cur -> next;
cur = pre -> next;
}
else
{
pre = cur;
cur = cur -> next;
}
}
}
交换第k和k + 1个节点的值
void func(LinkList &L,int k)
{
if(L == NULL || L -> next == NULL)return;
int i = 0;
LNode *p = L;
while(p&&i < k)
{
p = p -> next;
i ++ ;
}
int t;
t = p -> data;
p -> data = p -> next -> data;
p -> next -> data = t;
}
正负分离
头插负尾插正
void fun(LinkList &L)
{
if(L == NULL || L -> next == NULL)return;
LNode *p = L -> next;
LNode *r = L;
int i = 0;
while(p)
{
LNode *q = p -> next;
i ++ ;
if(p -> data < 0)
{
p -> next = L -> next;
L -> next = p;
if(i == 1)r = p;
}
else
{
r -> next = p;
r = p;
}
p = q;
}
r -> next = NULL;
}
循环链表
循环链表就是尾指头即可,而循环链表的遍历条件需要改为p!=L或者p->next!=L
单链表转化为循环链表
void trans(LinkList &L)
{
if(L == NULL || L -> next == NULL)return;
LNode *r = L;
while(r -> next != NULL)r = r -> next;
r -> next = L;
}
循环链表的遍历
void ShowLinkList(LinkList &L)
{
if(L == NULL || L -> next == NULL)return;
LNode *p = L -> next;
while(p != L)
{
cout << p -> data << ' ';
p = p -> next;
}
}
双链表
双链表解决了单链表节点只能存储直接后继的缺点,双链表中的节点可以存储之间前驱和直接后继,因此使得查找直接前驱和直接后继的时间复杂度均优化至O(1)级别,以下将介绍双向链表的操作函数以及实现原理
双链表的存储结构
typedef struct DuLNode
{
int data;
struct DuLNode *next;
struct DuLNode *pre;
}DuLNode,*DuLinkList;
双链表的功能
插入
void InsertDuLinkList(DuLinkList &L,int idx,int e)
{
DuLNode *p = L;
int i = 0;
while(p && i < idx - 1)
{
i ++ ;
p = p -> next;
}
if(!p || i > idx - 1)return;
DuLNode *s = new DuLNode;
s -> data = e;
s -> pre = p -> pre;
p -> pre -> next = s;
s -> next = p;
p -> pre = s;
}
删除
void DeleteDuLinkList(DuLinkList &L,int idx)
{
DuLNode *p = L;
int i = 0;
while(p && i < idx - 1)
{
i ++ ;
p = p -> next;
}
if(!p || i > idx - 1)return;
p -> pre -> next = p -> next;
p -> next -> pre = p -> pre;
}
顺序表和链表的比较
前面所介绍的两种存储结构:顺序表和链表都各自有各自的优缺点,因此不能绝对的、片面的认为哪儿一种存储结构优于哪儿一种,选用哪儿一种存储结构,应根据具体问题及具体来分析,从时间性能和空间性能两个方面来进行比较,从而进行最终的决策
空间性能的比较
存储空间的分配
顺序表的存储空间必须预先分配,元素个数有着一定的限制,容易造成存储空间的浪费或者空间溢出,而链表不需要为其预先分配空间,只要内存空间允许,链表中的元素个数就没有限制。
因此,当线性表的长度变化比较大,难以预估存储规模时,宜采用链表作为存储结构
存储密度的大小
链表的每个节点除了设置数据域来存储数据元素之外,还要额外设置指针域,用来存储指示元素之间逻辑关系的指针,从存储密度上来讲,这是不经济的。所谓存储密度就是指数据元素本身所占用的存储量和整个节点结构所占用的存储量之比
存储密度越大,存储空间的利用率就越高,显然,顺序表的存储密度为1,而链表的存储密度小于1.如果每个元素数据域占据的空间较校,则指针的结构性开销就占用了整个节点的大部分空间,这样存储密度较小
基于此,当线性表的长度变化不大,易于事先确定其大小时,为了节约存储空间,宜采用顺序表作为存储结构
时间性能的比较
存取元素的效率
顺序表是由数组实现的,它是一种随机存储结构,指定任意一个位置序号i,都可以在O(1)的时间内直接存取该位置上的元素,即取值操作的效率高;而链表是一种顺序存取结构,按位置访问链表中的第i个元素时,只能从表头开始依次向后遍历链表,直到找到第i个位置上的元素,时间复杂度为O(n),因此取值操作的效率低
基于此,若线性表的主要操作适合元素位置紧密相关的一系列取值操作,很少做插入或删除时,宜采用顺序表作为存储结构
插入和删除操作的效率
对于链表在确定插入或删除位置后,插入或删除操作无需移动数据,只需要修改指针,时间复杂度为O(1),而对于顺序表进行插入或者删除操作时,平均要移动表中近一半的节点,时间复杂度为O(n),尤其是当每个节点信息量较大时,移动节点的时间开销就相当可观
基于此,对于频繁进行插入与删除操作的线性表,宜利用链表作为存储结构
线性表的应用
线性表的合并
在这里我们仅仅介绍线性表的合并思想,假设我们有线性表LA和LB,遍历LA,将不存在LA中的LB元素插入LA扩充LA即可,在LA中利用定位函数定位LB中元素是否出现,从而决定扩不扩
有序表的合并
升序或者降序,基于分治思想的归并排序,在前文中有函数提及,查看前文内容即可,若是非分治法简单的归并排序,在顺序表当中创建一个空顺序表作为合并后的总表即可,在链表中设立三个指针分别用来归并思想即可
案例分析与实现
课程设计内容,目前用不到,设计时再作补充
本章小结
1.线性表的逻辑结构特性是指数据元素之间存在着线性关系,在计算机中表示这种关系的两类不同的存储结构是顺序存储结构和链式存储结构
2.对于顺序表,元素存储的相邻位置反映出逻辑上的线性关系,可借助数组来表示,给定数组的下标便可以存取相应的元素,可称为随机存储结构。而对于链表,其是依靠指针来反映其线性逻辑关系的,链表节点的存储都要从头指针开始,顺链而行,所以不属于随机存取结构,而属于顺序存取结构,不同的特点使得顺序表和链表有不同的适用情况
3.对于链表,除了常用的单链表之外,还有循环链表和双向链表,同样,它们也适用于不同的场景