线性表的类型定义
线性表是最常用且最简单的一种数据结构,一个线性表是n个数据元素的有限序列。在稍复杂的线性表中,一个数据元素可以由若干数据项组成。在这种情况下,常把数据元素称为记录,含有大量记录的线性表又称为文件。线性表中的数据类型可以是各种各样的,但同一线性表中的元素必定具有相同特性,即属于同一个数据对象。相邻数据元素之间存在序偶关系,也就是
a
i
ai
ai 领先于
a
i
−
1
a_{i-1}
ai−1,称
a
i
−
1
a_{i-1}
ai−1 是
a
i
a_i
ai 的直接前驱元素,
a
i
+
1
a_{i+1}
ai+1 是
a
i
a_i
ai 的直接后继元素。每个元素最多只有一个直接前驱,和一个直接后继。
线性表的元素个数定义为线性表的长度,当个数为0时,称为空表。
线性表的基本操作
初始化链表
InitList(& L)
{
操作结果:构造一个空的线性表
}
void InitList(&L)
{
L.length=0;
L.head=NULL;
}
销毁链表
DestroyList(&L)
{
初始条件:线性表L已存在
操作结果:销毁线性表L
}
清空链表
ClearList(&L)
{
初始条件:线性表L已存在
操作结果:将L重置为空表
}
int ClearList(&L)
{
(数据类型)*p=L.head;//p为表头地址
while(p)//当p不是NULL
{
L.head=p->next;//将链表的表头地址变为表头的下一个元素的地址。
delete p;//删除此前表头的节点空间
p=L.head;//将p指向当前的表头地址
}
return OK;
}
判断链表是否为空
ListEmpty(L)
{
初始条件:线性表L已存在
操作结果:若L为空表,则返回TRUE,否则返回FALSE
}
判断链表的长度
ListLength(L)
{
初始条件:线性表L已存在
操作结果:返回L中数据元素的个数
}
判读链表中第i个数据元素的值
GetElem(L,i,&e)
{
初始条件:线性表L已存在,1≤i≤ListLength(L)
操作结果:用e返回L中第i个数据元素的值
}
int GetElem(L,int i,&e)
{
if(i<1||i>L.length) return ERROR;
(链表元素数据类型) *p=L.head;
for(int k=1;k<i;k++)
{
p=p->next;//不断递推到下一个元素
}
e=p->data;
return OK;
}
判断L中第1个与e满足关系compare()的数据元素的位序,若不存在则返回0
LocateElem(L,e,compare())
{
初始条件:线性表L已存在
操作结果:判断L中第1个与e满足关系compare()的数据元素的位序,若不存在则返回0
}
int LocateElem(L,e,bool (*compare)(int a,int b))//指向函数的指针要带上参数列表
{
(链表元素数据类型) *p=L.head;
int k=1;
while(p)
{
if((*compare)(p->date,e)) return k;
else k++;
}
return FALSE;
}
判断一个元素的前驱
PriorElem(L,cur_e,&pre_e)
{
初始条件:线性表L已存在
操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,否则操作失败,pre_e无定义
}
判断一个元素的后继
NextElem(L,cur_e,&next_e)
{
初始条件:线性表L已存在
操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的前驱,否则操作失败,next_e无定义
}
在指定位置插入元素
ListInsert(&L,i,e)
{
初始条件:线性表L已存在,1≤i≤ListLength(L)+1
操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
}
int ListInsert(&L,int i,(链表元素数据类型)e)
{
if(i>L.length+1||i<0) return FALSE;//i的合法性检测
//申请一个节点,并进行初始化设置
(链表元素数据类型)*p;
p=new (链表元素数据类型);
p->next=NULL;
p->data=e;
if(i==1)//1位置进行特殊处理
{
p->next=L.head;
L.head=p;
}
else
{
//寻找第i-1号元素
(链表元素数据类型)*q=L.head;
for(int l=1;k<i-1;k++)
{
q=q->next;
}
p->next=q->next;
q->next=p;
}
L.length++;
return OK;
}
删除第i个元素
ListDelete(&L,i,&e)
{
初始条件:线性表L已存在且非空,1≤i≤ListLength(L)
操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1
}
int ListDelete(&L,int i,(链表元素数据类型)&e)
{
//i合法性验证
if(i<i||i>L.length) return ERROR;
//第一个节点特殊处理
if(i==1)
{
(链表元素数据类型)*P;
p=L.head;
L.head=p->next;
e=p->data;
}
else
{
//先找到要删除节点的前一个节点
(链表元素数据类型)*p=L.head;
for(int k=0;k<i-1;k++)
{
p->p.next;
}
(链表元素数据类型)*q=p->next;
p->next=q->next;//直接跳过要删除的元素
delete q;//删除
}
return OK;
}
将两个有序链表合并为一个有序链表
设置头指针为La和Lb的单链表分别为线性表LA和LB的的存储结构,现要归并La和Lb得到单链表Lc,
需设立3个指针pa,pb,pc,其中pa,pb分别指向La表和Lb表中当前待比较插入的结点,
而pc指向Lc表中当前最后一个结点,若pa->data <= pb->data,则将pa所指向结点链接到pc所指向结点之后,
否则将pb所指结点链接到pc所指结点之后。显然,指针的初始状态为:当LA和LB不为空表时,pa和pb分别指向La和Lb表中的第一个结点,
否则为空;pc指向空表Lc中的头节点。由于链表的长度是隐含的,则第一个循环执行的条件是pa和pb皆不为空,
当其中一个为空时,就说明有一个表的元素已归并完,则只需要将另一个表的剩余元素都加入pc所指结点之后就可以
void Merge_L(LinkList &La,LinkList &Lb,LinkList &Lc)
{
//已知单链表La和Lb的元素按值非递减排序。
//归并La和Lb得到新的单链表Lc,Lc的元素也按值非递减排序
pa=La->next;//pa代表首元素
pb=Lb->next;
Lc=pc=La;//用La的头结点作为Lc的头结点
while(pa&&pb)//两者都不指向NULL
{
if(pa->data <= pb->data)
{
pc->next=pa;pc=pa;pa=pa->next;//将pc的next指针指向当前的pa,之后将pc指针移动到当前pa,最后将pa后移一位;
}
else
{
pc->next=pb;pc=pb;pb=pb->next;
}
}
pc->next=pa?pa:pb;//判断是哪一个非空
free(Lb);//因为直接将La作为头结点了,所以b没用 释放
}
以学生管理系统为例子
#include<iostream>
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
using namespace std;
typedef struct student
{
char sno;
int g;
student* next;
}stu;
typedef struct STU
{
int length;
student* head;
} STU;
void Initstu(STU &first){
first.length = 0;
first.head = NULL;
}
//查找
stu* Locate(STU first, int i)//返回第i个元素
{
int x = 1;
stu* p = first.head;
while (p != NULL && x != i)//递推寻找第i个元素
{
//first = *first.next;
p = p->next;
x++;
}
if (x + 1 < i)return NULL;
return p;//返回第i个元素
}
//插入
int insertstu(STU& first, stu x, int i)//将元素x插入到第i-1号节点与第i号节点之间
{
stu* p = Locate(first, i - 1);//寻找第i-1号元素的地址
if (p == NULL)return ERROR;//如果没有第i-1号元素
stu* q = (stu*)malloc(sizeof(stu));//申请一个stu类型的指针
q->g = x.g, q->sno = x.sno;//赋值
q->next = p->next;//将x的next指针指向第i号节点。
p->next = q;//将第i-1号节点的指针指向x
return OK;
}
//删除第i号元素
int delstu(STU& first, int i)
{
stu* p = Locate(first, i - 1);//寻找第i-1号元素的地址
if (p->next == NULL)return ERROR;
stu* q = p->next;//p为第i号节点的地址
p->next = q->next;//将i-1号节点的next指针指向i+1,直接跳过i等于删除。
free(q);
return OK;
}
//更新第i个学生
int renewstu(STU& first, int i, char sno1, int g1)
{
stu* p = Locate(first, i);//寻找第i号元素的地址
if (p == NULL)return ERROR;//如果没有第i号元素
p->g = g1, p->sno = sno1;
return OK;
}
void frestu(STU& first)
{
first.head = NULL;
first.length = 0;
cout << "Successfully deleted" << endl;
}
int main()
{
STU fir;
Initstu(fir);
/*cout << "请输入第一个学生的信息(学号与成绩)" << endl;
int a;
char b;
cin >> b >> a;
stu p;
p.g = a, p.sno = b;*/
while (1)
{
char x;
cout << "请输入接下来的所需操作" << endl;
cout << "将某个新的学生信息插入到某个位置请输入 i" << endl;
cout << "删除第i个学生的信息请输入 d" << endl;
cout << "查找第i个学生的信息请输入 l" << endl;
cout << "修改第i个学生的信息请输入 r" << endl;
cout << "删除所有已经录入的学生信息请输入 f" << endl;
cout << "退出菜单,请选择e" << endl;
cin >> x;
switch (x)
{
case 'i':
{
cout << "请输入要插入的位置以及该学生的学号和成绩" << endl;
int i, g1;
char sno1;
cin >> i >> g1 >> sno1;
stu y;
y.g = g1, y.sno = sno1;
int k = insertstu(fir, y, i);
if (k == 1) cout << "Success" << endl;
else cout << "Fail" << endl;
break;
}
case 'd':
{
cout << "请输入要删除信息的学生序号" << endl;
int i;
cin >> i;
int k = delstu(fir, i);
if (k == 1) cout << "Success" << endl;
else cout << "Fail" << endl;
break;
}
case'l':
{
cout << "请输入要查找的学生的序号" << endl;
int i;
cin >> i;
stu* p = Locate(fir, i);//返回第i个元素的地址
cout << "该学生的学号和成绩为:" << p->sno << " " << p->g << endl;
break;
}
case'r':
{
cout << "请输入要更新的学生的序号以及该学生的学号和成绩" << endl;
int i, g1;
char sno1;
cin >> i >> g1 >> sno1;
int k = renewstu(fir, i, sno1, g1);
if (k == 1) cout << "Success" << endl;
else cout << "Fail" << endl;
break;
}
case'f':
{
frestu(fir);
break;
}
case'e':
break;
}
}
}
循环链表
循环链表是另一种形式的链式存储结构。他的特点是表中的最后一个结点的指针域指向头结点,整个链表形成一个环。由此,从表中任一结点出发均可均可找到表中其他结点。循环链表的操作和线性链表基本一致,差别仅在于算法中的循环条件不是p或p->next是否为空,而是它们是否等于头指针。有时候也可以只设置尾指针,这样在合并的时候就直接将一个链表的表头接入到另一个链表的表尾就可以。仅需改变两个指针值。O(1)。
双向链表
以上讨论的链表存储结构的结点中只有一个指示直接后继的指针域,由此,从某个结点出发只能顺指针往后寻查其他结点。若要寻查结点的直接前驱,则需从表头指针出发。换句话说,在单链表中,NextElem的执行时间为O(1),但是PriorElem的执行时间为O(n)。为克服单链表的这种单向性的缺点,可利用双向链表。
顾名思义,在双向链表的结点中有两个指针域,其一指向直接后继,,另一个指向直接前驱
--------------------------------线性表的双向链表存储结构------------------------------------
typedef struct DulNode{
ElemType data;
struct DulNode *prior;
struct DulNode *next;
}DulNode,* DuLinkList;
在双向链表中,若d为指向表中某一结点的指针(即d为DuLinkList型的变量)则显然有
d->next->prior == d->prior->next == d;
在双向链表中,有些操作如:LinkLength,GetElem和LocateElem 等仅需涉及一个方向的指针,则他们的算法描述和线性链表的操作相同,但在插入,删除时有很大的不同,在双向链表中需同时修改两个方向上的指针。
Status LinkInsert-Dul(DulLinkLIst &L,int i,ElemType e){
//在带头结点的双链循环线性表L中第i个位置之前插入元素e
//i的合法值为i≤i≤表长+1
if(!(p=GetElemP_Dul(L,i)//在L中确定插入位置指针p
return ERROR;//i等于表长加1时,p指向头结点;i大于表长加1时,p=NULL;
if(!(s=(DuLinkList)malloc(sizeof(DulNode)))) return ERROR;
s->data=e;
s->prior=p->prior;p->prior->next=s;
s->next=p;
p->prior=s;
return OK;
}//ListInsert_Dul
Status ListDelete_Dul(DuLinkList &L,int i,ElemType &e){
//删除带头结点的双链循环线性表L的第i个元素,i的合法值为1≤i≤表长
if(!(p=GetElemP_Dul(L,i))) //在L中确定第i个元素的位置指针p
return ERROR; //p=NULL 即第i个元素不存在
e=p->data;
p->prior->next =p->next;
p->next->prior=p->prior;
free(p);
return OK;
}//ListDelete_Dul;
在很多情况下,链表是线性表的首选存储结构,然而,它也存在着实现某些基本操作,如求线性表的长度时不如顺序存储结构的缺点;另一方面,由于在链表中,结点之间的关系用指针来表示,则数据元素在线性表中的“位序”的概念已淡化,而被数据元素在线性链表中的“位置”所代替。