本节为线性表内容,回到总目录:点击此处
本部分目录
线性表
特点: 同一性(线性表由同类数据元素组成)
有穷性 (有限个元素组成)
有序性 (相邻元素之间存在着序偶关系<
a
i
,
a
i
+
1
a_i,a_{i+1}
ai,ai+1>
抽象数据类型定义:
ADT LinearList {
数据元素:D={}
关系:S = }基本操作:
InitList(L) 操作前提:L为未初始化线性表
操作结果:将L初始化为空表DestoryList(L) 操作前提:线性表L已存在
操作结果:将L销毁ClearList(L) 操作前提:线性表L已存在
操作结果:将表L置为空表}ADT LinearList
线性表的顺序储存
–>用一组地址连续的存储单元一次储存线性表中的各个元素,使得线性表中在逻辑结构上相邻的数据元素储存在相邻的物理储存单元中。
–>采用顺序存储结构的线性表通常称为顺序表
#define maxsize = ;
typedef struct
{
ElemType elem[maxsize];
int last; //记录线性表最后一个元素在数组elem[]中的位置(下标),空表置为-1。
}SeqList;
顺序存储结构线性表的基本运算
-
查找
按序号查找:线性表L中第i个数据元素,其结果为L.elem[i-1]或L->elem[i-1]
按内容查找:要求查找线性表L中与给定值e相等的数据元素,其结果为线性表L中找到与e相等的元素,则返回其序号,若未找到,则返回一个“空序号”,-1int Locate (SeqList L, ElemType e) { i = 0; while ((i <= L.last) && (L.elem[i] != e)) i++; if (i <= L.last) return (i); else return (-1); }
-
插入
在线性表的第i个位置插入一个元素eint InsList (SeqList *L, int i, ElemType e) { int k; if ((i < 1) || (i > L -> last+2)) //检查插入位置是否满足要求 // 0 <= i - 1 <= L->last+1; { return error; } if (L -> last >= maxsize-1) //检查表是否已满 { return error; } for (k = L -> last; k >= i-1; k--) { L -> elem[k+1] = L -> elem[k]; } L - > elem[i - 1] = e; L - > last ++; return (OK); }
//执行循环的次数 n - i + 1; 时间复杂度与i有关
//顺序标插入算法平均需移动一半结点 n/2 -
删除
将表的第i个元素删除int DelList(SeqList *L, int i, ElemType *e) { int k; if ((i < 1) || (i > L->last+1)) { return error; //检查删除位置 } *e = L->elem[i-1]; for (k = i; i <= L->last; k++) { L->elem[k-1] = L -> elem[k]; } L -> last --; return(OK); }
//循环执行次数 n - i —与i的取值有关 平均 (n -1) /2
-
合并
将两个顺序表LA和LB合并,其元素均为非递减的有序排列,合并成LCvoid Merge(SeqList *LA; SeqList *LB; SeqList *LC) { i = 0; j = 0; k = 0; while (i <= LA -> Last && j <= LB -> last) { if (LA -> elem[i] <= LB -> elem[j]) LC -> elem[k] = LA -> elem [i]; i++; k++; else LC -> elem[k] = LB -> elem[j]; j++; k++; } while (i <= LA->last) { LC -> elem[k] = LB -> elem[j]; i++; k++; } while (j <= LB->last) { LC -> elem[k] = LB -> elem[j]; j++; k++; } LC -> last = LA -> last + LB -> last; }
优点:·无需为表示结点间的逻辑关系而增加额外的存储空间;
· 可方便地随机存取表中的任一元素缺点:·插入或删除运算不方便
·存储分配需要预先进行静态分配,当表比较长时候,难以分配合适空间
单链表
注意事项(*名词解释)
概念················· | 解释 |
---|---|
头指针 | 1.头指针具有标识作用,故常用头指针冠以链表的名字,可类比数组 |
2.指向链表中第一个节点的存储位置,当存在头结点时指向头结点,否则指向首结点 | |
3.勿论链表是否为空,头指针均不为空 | |
4.只要不是循环链表一定存在头指针 | |
头结点 | 1.为了操作的统一与便利而设置的,位于首结点之前的结点,其数据域一般无意义 |
2.不是链表必须的 | |
尾指针 | 1.在单向循环链表中,通常只保存一个尾指针。因为尾指针的下一个节点就是头节点,方便在头尾进行操作 |
尾结点 | 1.链表中最后一个结点 --||一般尾结点的指针指向为NULL |
采用链式存储结构的线性表成为链表
动态链表、静态链表;单链表、循环链表、双链表。
结点(Node) — 数据域 | 指针域 (牺牲空间效率换取时间效率)
typedef struct Node
{
ElemType data;
struct Node *next;
}Node,*LinkList;
//定义结点。*LinkList为结构指针类型
建立单链表 (头插法与尾插法)
· 头插法建表
Linklist CreateFromHead()
{
LinkList L; Node *s; int flag = 1;
/*设置一个标志,初始值为1,当输入“$”时,flag为0,建表结束*/
L = (Linklist) malloc(sizeof(Node)); //为头结点分配存储空间
L -> next = NULL;
while(flag)
{
c = getchar();
if (c != "$")
{
s = (Node *)malloc(sizeof(Node));
s -> data = c;
s -> next = L -> next;
L -> next = s;
}
else flag = 0;
}
}
·尾插法建表
Linklist CreateFromTail()
{
Linklist L;
Node *r, *s;
int flag = 1;
L = (Node *)malloc(sizeof(Node));
L -> next = NULL;
r = L;
while (flag)
{
c = getchar();
if (c != "$")
{
s = (Node *)malloc(sizeof(Node));
s -> data = c;
r -> next = s;
r = s;
}
else
{
flag = 0;
r -> next = NULL;
}
}
}
单链表的操作(查找、长度、删除、合并、插入、差集)
·单链表的查找
-
按序号查找
//在带头结点的单链表L中查找第i个结点,若找到(1<=i<=n),则返回该结点储存的位置,否则返回NULL。 Node* Get(Linklist L, int i) { Node *p; p = L; j = 0; while ((p -> next != NULL) && (j < i)) { p = p -> next; j ++; } if (i == j) return p; else return NULL; }
-
按值查找
Node* Locate(Linklist L, ElemType key) { Node *p; p = L -> next; while (p != NULL) { if(p -> data != key) p = p -> next; else break; } return p; }
·求单链表的长度
int ListLength(Linklist L)
{
Node *p;
p = L -> next;
j = 0;
while (p != NULL)
{
p = p -> next;
j++;
}
return j;
}
·单链表的插入操作 (需要预留指针pre 用来指示当前位置)
void InsList(Linklist L, int i, ElemType e)
{
/*在带头结点的单链表L中第i个结点之前插入值为e的新结点*/
Node *pre, *s;
pre = L;
int k = 0;
while (pre != NULL && k < i - 1)
{
pre = pre -> next;
k = k + 1;
}
if (k != i - 1)
{
cout << "插入位置不合理";
return error;
}
s = (Node *)malloc(sizeof(Node));
s -> data = e;
s -> next = pre -> next;
pre -> next = s;
}
·单链表的删除
单链表的删除应注意!!
删除算法中的循环条件(pre -> next != NULL && k < i -1)与前插算法中的循环条件(pre != NULL && k < i -1)不同,因为前插时的插入位置有m+1个[可以在指针末尾插入添加],而删除操作的合法位置只有m个,若使用和前插算法相同的循环条件,则会出现指针指空的情况。
void DelList(Linklist L, int i, ElemType *e)
{
/*在带头节点的单链表L中删除第i个元素,并保存其值到变量*e中*/
Node *p, *r;
p = L;
int k = 0;
while (p -> next != NULL && k < i - 1)
{
p = p -> next;
k = k + 1;
}
if (k != i - 1)
{
return error;
}
r = p -> next;
p -> next = p -> next -> next;
*e = r -> data;
free(r);
}
·求两个集合的差
A - B -----> LC 求差集
void Difference(Linklist LA, Linklist LB)
{
Node *pre,*p,*r,*q;
pre = LA;
p = LA -> next;
while (p != NULL)
{
q = LB -> next;
while(q != NULL && q -> data != p -> data) q = q -> next;
if (q != NULL)
{
r = p;
pre -> next = p -> next;
p = p -> next;
free(r);
}
else
{
pre = p;
p = p -> next;
}
}
}
//方法二:
void Diff(LinkList &A,LinkList &B)
{
LinkList pc = A;
Node *pa = A -> next, *pb = B -> next;
while(pa && pb)
{
if(pa -> data == pb -> data)
{
pa = pa -> next;
pb = pb -> next;
}
else if( pa-> data > pb -> data)
{
pb = pb -> next;
}
else if( pa -> data < pb -> data)
{
pc -> next = pa;
pa = pa -> next;
pc = pc -> next;
}
if(!pb) pc -> next = pa;
}
return;
}
·合并两个有序的单链表
Linklist MergeLinklist(Linklist LA, Linklist LB)
{
Node *pa, *pb;
Linklist LC;
pa = LA -> next;
pb = LB -> next;
LC = LA;
LC -> next = NULL;
r = LC; //r的初始值为LC 且 始终指向LC的表尾
//两个表中均未处理完,选择比较小的值结点插入到新表LC中
while (pa != NULL && pb != NULL)
{
if (pa -> data <= pb -> data)
{
r -> next = pa;
r = pa;
pa = pa -> next;
}
else
{
r -> next = pb;
r = pb;
pb = pb -> next;
}
}
if(pa) //若LA未完,则将表LA中后续元素链到新表LC表尾
{
r -> next = pa;
}
else //否则则处理LB
{
r -> next = pb;
}
free(LB);
return(LC);
}
循环链表
单循环链表(初始化与建立、循环单链表的合并)
表中最后一个结点的指针指向第一个结点或表头结点
·循环单链表的初始化和建立循环单链表
InitCLinkList(Linklist *CL)
{
*CL = (Linklist)malloc(sizeof(NOde));
(*CL) - > next = *CL;
}
//初始化
void CreateCLinklist(Linklist CL)
{
Node *r, *s;
char c;
r = CL;
c = getchar();
while (c != "$")
{
s = (Node *)malloc(sizeof(Node));
s -> data = c;
r -> next = s;
r = s;
c = getchar();
}
r -> next = CL; //让最后一个结点的next链域指向头结点 //与单向链表的区别
}
·循环单链表的合并
重在理解!!!尾插法||头插法!!
//将两个带有头结点的循环单链表LA、LB合并未一个循环单链表,其头指针为LA。
Linklist merge_1(Linklist LA, Linklist LB)
{
//将两个采用头指针的循环单链表进行首尾相连
Node *p, *q;
p = LA;
q = LB;
//找表尾
while (p -> next != NULL) p = p -> next;
while (q -> next != NULL) q = q -> next;
q -> next = LA;
p -> next = LB -> next;
free(LB);
return (LA);
}
--使用尾指针直接指向链表尾部--
--好处:带尾指针的循环链表,直接指向链表的尾部,并且rare->next即为循环链表的头指针--
Linklist merge_2(Linklist RA, Linklist RB) //带尾插法的循环链表的合并
{
// RA RB 分别表示的是指向A,B链表最后一个元素的指针
//此算法将两个采用尾指针的循环链表首尾连接起来
Node *p;
p = RA -> next; //保存RA的头结点地址
RA -> next = RB -> next -> next; //链表RB的开始结点链到RA的终端结点之后
free(RB -> next); //释放链表RB的头结点
RB -> next = p; //链表RA的头结点链到RB的终端结点之后
rerurn RB; //返回新循环链表的尾指针
}
双向链表
双向链表的组成:(一般也由头指针head唯一确定)
每一个结点均有:
数据域 (data)
左链域(back) 指向前驱结点
右链域(next) 指向后继
双向链表的结构定义:
typedef struct Dnode
{
ElemType data;
struct DNode *prior, *next;
}DNode, *DoubleList;
p == p -> back -> next == p -> next -> back
双向链表的操作
· 插入操作:
int DlinkIns(DoubleList L, int i, ElemType e)
{
DNode *s, *p;
... //检查插入位置是否合理
... //然后定位到第i个结点,并让指针p指向它
s = (DNode *)malloc(sizeof(DNode));
if(s)
{
s -> data = e;
s -> prior = p -> prior;
p -> prior -> next = s; //断开
s -> next = p; //加线
p -> prior = s; //加线
return true;
}
else
return false;
}
·删除操作
int DlinkDel(DoubleList L, int i, ElemType *e)
{
DNode *p;
... //检查删除位置是否合理
... //定位到第i个结点,并让指针p指向它
*e = p -> data;
p -> prior -> next = p -> next;
p -> next -> prior = p -> prior;
free(p);
return true;
}
*静态链表
链表的排序算法
·选择排序
void sort02(Linklist &L) //选择排序
{
Node *r,*p;
p = L -> next;
while (p -> next != NULL) //一重循环
{
r = p -> next;
while (r != NULL) //二重循环
{
if (p -> data > r -> data)
{
int t = p -> data;
p -> data = r -> data;
r -> data = t;
}
r = r -> next;
}
p = p -> next;
}
}
·冒泡排序
void Bubble_sort(Linklist &L) //冒泡排序
{
Node *cur,*tail;
tail = L -> next;
cur = L -> next;
while (tail != NULL) //找到尾节点
{
tail = tail -> next;
}
while (cur != tail) //双层循环,冒泡排序
{
while(cur -> next != tail)
{
if(cur -> data > cur -> next -> data)
{
int t = cur-> data;
cur -> data = cur -> next -> data;
cur-> next -> data = t;
}
cur = cur -> next;
}
tail = cur;
cur = L -> next;
}
}
·选择排序
线性表的应用~一元多项式
一元多项式的存储
· 一元多项式的顺序存储表示
[适合非零项较多] 只存储该一元多项式各项的系数
[适合非零项较少] 存储非零项,存储非零项系数和非零项指数这两项
· 一元多项式的链式存储表示
struct Polynode
{
int coef;
int exp;
Polynode * next;
}Polynode, *Polylist;
建立(输入多项式的系数和指数,用尾插法建立一元多项式的链表。以输入系数0为结束标志,并约定建立链表时总是按指数从低到高排列)
Polylist polycreate()
{
Polynode *head, *rear, *s;
int c, e;
rear = head = (Polynode *)malloc(sizeof(Polynode));
/*建立多项式的头结点,rear始终指向单链表的尾部*/
scanf("%d,%d",&c,&e); //多项式系数和指数项
while(c != 0)
{
s = (Polynode *)malloc (sizeof(Polynode));
s -> coef = c;
s -> exp = e;
rear -> next =s; //尾插法
rear = s;
scanf("%d,%d",&c,&e); //多项式系数和指数项
}
rear -> next = NULL;
return(head);
}
两个一元多项式相加
算法实现:(多种情况需要分类讨论)
vpid polyadd(Polylist polya, Polylist polyb)
{
//和多项式存放在多项式polya中,并姜多项式polyb删除
Polynode *p, *q, *tail, *temp;
int sum;
p = polya -> next;
q = polyb -> next;
tail = polya; //tail指向和多项式的尾结点
while (p != NULL && q != NULL)
{
if (p -> exp < q -> exp)
{
tail -> next = p;
tail = p;
p = p -> next;
}
else if (p -> exp == q -> exp)
{
sum = p -> coef + q -> coef; //如果指数相等,则将系数相加
if (sum != 0)
{
p -> coef = sum;
tail -> next = p;
tail = tail -> next;
p = p -> next;
q = q -> next;
}
else //若系数和为0,则删除结点p、q,将指针指向下一个结点
{
temp = p -> next;
free (p);
p = temp;
temp = q -> next;
free (q);
q = temp;
}
}
else //将q结点加入到和多项式中
{
tail -> next = q;
tail = q;
q = q -> next;
}
}
if (p != NULL)
{
tail -> next = p; //若多项式A中还有剩余,则将剩余的结点都加入到和多项式中
}
else
{
tail -> next = q; //否则将B中的结点加入到和多项式中
}
}
◾线性表 总结与反思
空间
顺序表的存储空间是静态分配的,在程序执行之前必须明确规定它的存储规模,如果线性表的长度变化较大,则存储规模难以预先确定。
线性表的存储空间是动态分配的,只要空间尚有空余,就不会产生溢出。
存储密度,结点数据本身所占的存储量和整个结点结构所占的存储量之比,存储密度越大,存储空间的利用率越高,顺序表的存储密度为1,而链表的存储密度小于1。
时间
顺序表由向量实现,是一种随机存取结构,对表中任一结点都可以在O(1)时间内直接地存取,而链表中的结点则需要从头指针起顺着链查找才能取得。—>便于查找
链表如果要进行插入删除操作,都只需要修改指针。对于频繁需要插入删除操作的线性表,则宜采用链表作为存储结构。但若表的插入或删除操作主要发生在表的首尾两端,则宜采用带尾指针的单循环链表。