线性表(List):零个或多个数据元素的有限序列。
定义如下:
ADT 线性表(List)
Data
线性表的数据对象集合为{a~1~,a~2~,……,a~n~},每个元素的类型均为DataType。其中,除第一个元素a1外,每一个元素有且只有一个直接前驱元素,除了最后一个元素a~n~外,每一个元素有且只有一个直接后继元素。数据元素之间的关系是一对一的关系。
Operation
InitList(*L): 初始化操作,建立一个空的线性表L。
ListEmpty(L): 若线性表为空,返回true,否则返回false。
ClearList(*L): 将线性表清空。
GetElem(L,i,*e): 将线性表L中的第i个位置元素值返回给e。
LocateElem(L,e): 在线性表L中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号表示成功;否则,返回0表示失败。
ListInsert(*L,i,e): 在线性表L中的第i个位置插入新元素e。
ListDelete(*L,i,*e):删除线性表L中第i个位置元素,并用e返回其值。
ListLength(L): 返回线性表L的元素个数。
endADT
线性表的顺序存储结构:指的是用一段地址连续的存储单元依次存储线性表的数据元素。
以下为线性表的顺序存储结构示意图:
#define MAXSIZE 20 //存储空间初始分配量
typedef int ElemType; //ElemType类型根据实际情况而定,这里假设为 int
typedef struct
{
ElemType datta[MAXSIZE]; //数组存储数据元素,最大值为 MAXSIZE
int length; //线性表当前长度
}SqList;
描述顺序存储结构需要三个属性:
存储器中的每个存储单元都有自己的编号,这个编号称为地址。
线性表中可以进行插入和删除操作,因此分配的数组空间要大于等于当前线性表的长度。
(C语言中的数组的下标是从0开始的)
地址计算方法:LOC(ai) = LOC(ai) + (i -1) * c (c为一个数据元素需要的存储单元)
#include
#include
#include
//函数状态码定义
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
//顺序表的存储结构定义
#define LIST_INIT_SIZE 100 //初始分配存储空间
#define LISTINCREMENT 10 //存储空间分配增量
typedef struct
{
int *elem; //存储空间基地址
int length; //表中元素的个数
int listsize; //表容量大小
}SqList; //顺序表类型定义
int ListInsert_Sq(SqList &L, int pos, ElemType e);
int ListDelete_Sq(SqList &L, int pos, ElemType &e);
int ListLocate_Sq(SqList L, ElemType e);
void ListPrint_Sq(SqList L);
//结构初始化与销毁操作
int InitList_Sq(SqList &L)
{
//初始化L为一个空的有序顺序表
L.elem=(ElemType *)malloc(LIST_INIT_SIZE*sizeof(ElemType));
if(!L.elem) exit(OVERFLOW); //申请失败
L.listsize = LIST_INIT_SIZE;
L.length=0; //表长为0
return OK;
}
//主函数
int main()
{
SqList L; //构造一个表
if(InitList_Sq(L)!= OK) //初始化失败
{
printf("InitList_Sq: 初始化失败!\n");
return -1;
}
printf("1:插入 2:删除 3:查找 4:输出 0:退出\n");
for(int i=1; i<=10; i++)
ListInsert_Sq(L,i,i); //初始化表
printf("初始顺序表为:\n");
ListPrint_Sq(L); //输出表
int operationType; //操作种类
printf("\n请输入要进行的操作:");
scanf("%d",&operationType); //输入操作
while(operationType != 0)
{
if(operationType == 1)
{ //插入操作
int pos,elem;
printf("请输入要插入的位置和值:");
scanf("%d %d",&pos,&elem);
ListInsert_Sq(L,pos,elem);
printf("插入后的顺序表为:\n");
ListPrint_Sq(L);
}
else if(operationType == 2)
{ //删除操作
int pos;int elem;
printf("请输入要删除位置:");
scanf("%d",&pos);
ListDelete_Sq(L,pos,elem);
printf("删除的值为:");
printf("%d",elem);
printf("\n删除后的顺序表为:\n");
ListPrint_Sq(L);
}
else if(operationType == 3)
{ //查找定位操作
int elem;
printf("请输入要查找的值:");
scanf("%d",&elem);
int pos = ListLocate_Sq(L,elem);
if(pos>=1 && pos<=L.length)
{
printf("该值的位置是:");
printf("%d",pos);
}
else
printf("该值不存在!\n");
}
else if(operationType == 4)
{ //输出操作
printf("当前的顺序表为:\n");
ListPrint_Sq(L);
}
else if(operationType == 0)
{
return 0;
}
printf("\n请输入要进行的操作:");
scanf("%d",&operationType);//输入操作
}
system("pause");//防止窗体闪退
return 0;
}
//插入操作
int ListInsert_Sq(SqList &L,int pos,int e)
{
int *newbase;//定义扩容后的首地址
if(pos>=1 && pos<=L.length+1)//判断给的pos是否在顺序表长度之内
{
if(L.length >= L.listsize)//如果L.length已经达到或超出设定值,需要扩容
{
newbase = (int *)realloc(L.elem,(L.listsize+LISTINCREMENT)*sizeof(int));//给newbase动态分配一个长度为LISTINCREMENT的新空间
if(! newbase)//如果分配失败则返回错误
return ERROR;
L.elem = newbase;//新分配空间的基地址
L.listsize += LISTINCREMENT;//现在的空间长度等于原来空间长度加上新分配的空间长度
}
int *p,*q;
p = &(L.elem[pos-1]);//将原来的pos位置元素的地址分配给指针p
for(q=&(L.elem[L.length-1]); q>=p; --q)
{
*(q+1) = *q; //将原来顺序表最后一个位置数据的地址分配给q,然后从后往前一次将数据向后移动一位
}
*p = e;//将数据e放到pos位置
++L.length;//表长加1
return OK;
}
else//pos不在顺序表的长度之内
return OVERFLOW;
}
//删除操作
int ListDelete_Sq(SqList &L,int pos,int &e)
{
if(pos>=1 && pos<=L.length)//判断pos是否在顺序表的长度之内
{
int *q;
e = L.elem[pos-1];//将删掉的值赋给e
for(q=&(L.elem[pos-1]); q<=&(L.elem[L.length-1]); ++q)
{
*q = *(q+1); //将pos位置以后的元素依次向前移动一位
}
L.length = L.length-1; //表长减1
}
else//pos不在顺序表的长度之内
return OVERFLOW;
}
//查找定位操作
int ListLocate_Sq(SqList L,int e)
{
int a=-1;//给a赋初值,无论查找的数据在第几个,都不可能在第-1个
for(int i=0; i<=L.length-1; i++)
{
if(L.elem[i] == e)
{
a = i; //查找成功,退出循环
break;
}
}
if(a>=0 && a<=L.length-1)
return a+1; //返回找到的元素的位置
else
return ERROR;//查找失败
}
//输出操作
void ListPrint_Sq(SqList L)
{
for(int i=0; i<=L.length-2; i++)
{
printf("%d ",L.elem[i]);
}
printf("%d ",L.elem[L.length-1]);
}
线性表的链式存储结构的特点是用一组任意的 存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续 的)。为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称作指针或链。这两部分信息组成数据元素ai的存储映像。称为结点(Node)。n个结点(ai的存储映像)链结成一个链表,即为线性表的链式存储结构。因为此链表的每个结点中只包含一个指针域,故又称线性链表或单链表。
链表中第一个结点的存储位置叫做头指针,最后一个结点不存在直接后继,规定,线性表的最后一个结点指针为“空”(通常用NULL或“ ^ ”符号表示)。
为了对链表操作更加方便,在单链表的第一个结点前附设一个结点,称为头结点,头结点的数据域不存储任何信息(可以存储表长等附加信息),头结点的指针域存储指向第一个结点的指针。
//线性表的单链表存储结构
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList; //定义LinkList
#include
#include
#include
typedef struct Node
{
int data;
struct Node *next;
}Node,*Linklist;
//创建链表
void List_create(Linklist &L)
{
int m; //m为结点数
Linklist p;
printf("请输入结点数:");
scanf("%d",&m); //输入结点数
L = (Linklist)malloc(sizeof(Node));
p = L;
for(int i=0; i<m; i++) //循环输入结点数据
{
p->next = (Linklist)malloc(sizeof(Node));
p = p->next;
printf("请输入第%d个结点的值:",i+1);
scanf("%d",&p->data); //输入结点数据
}
p->next = NULL;
}
//输出链表
void List_display(Linklist L)
{
Linklist p;
p = L->next;
printf("\n现在的链表为:\n");
printf("%d ",p->data);
p = p->next;
while(p)
{
printf("%d ",p->data);
p = p->next;
}
printf("\n\n");
}
//查找操作
int List_get(Linklist L,int i)
{
int j;
int e;
Linklist p; //声明一指针p
p = L->next; //指针p指向链表L的第一个结点
j = 1; //j是计数器
while(p && j<i) //*p不为空且计数器j还没有等于i(即还没有到达要查找的那个结点)
{
p=p->next; //p指向下一个结点
j++; //计数器+1
}
if(!p || j>i) return -1; //第i个结点不存在
e = p->data; //读取第i个结点的数据
return e;
}
//插入操作
int List_insert(Linklist L,int i,int e)
{//在L的第i个结点位置之前插入新的数据元素e
int j;
Linklist p,s;
p = L;
j = 1; //j是计数器
while(p && j<i) //寻找第i-1个结点
{
p = p->next;
j++;
}
if(!p || j>i) return -1; //第i个结点不存在
s = (Linklist)malloc(sizeof(Node)); //生成新结点
s->data = e; //将要插入的值赋值给s
s->next = p->next; //将p的后继结点赋值给s的后继
p->next = s; //将s赋值给p的后继
return 0;
}
//删除操作
int List_delete(Linklist L,int i)
{//删除L的第i个结点,并用e返回其值
int e,j; // e记录删去的值
Linklist p,q; //q为临时变量
p = L;
j = 1; //j是计数器
while(p->next && j<i) //寻找第i-1个结点
{
p = p->next;
j++;
}
if(!(p->next) || j>i) return -1; //第i个结点不存在
q = p->next;
p->next = q->next; //将q的后继赋值给p的后继
e = q->data; //将q结点中的数据传给e
free(q); //回收此结点,释放内存
return e;
}
//主函数
int main()
{
int i,j,e;
Linklist La;
List_create(La);
List_display(La);
printf("请输入要查找第几个结点:");
scanf("%d",&i);
e = List_get(La,i);
printf("第%d个结点的数据为:%d",i,e);
printf("\n\n请输入要插入的位置:");
scanf("%d",&i);
printf("请输入要插入的值:");
scanf("%d",&j);
List_insert(La,i,j);
List_display(La);
printf("请输入要删除的位置:");
scanf("%d",&i);
e = List_delete(La,i);
printf("删除的第%d个结点的值为%d\n",i,e);
List_display(La);
system("pause"); //防止窗体闪退
return 0;
}
将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表成为单循环链表,简称循环链表。
此时,p->next ≠ 头结点,则循环未结束。和单链表的差别仅在于,判别链表中最后一个结点的条件不再是“后继是否为空”,而是“后继是否为头结点”。
与单链表相比,优势在于可以从任何一个结点开始,顺序向后访问到达任意结点,但是依然只能在当前结点后插入和删除。
定义:在单链表的每个结点中,再设置一个指向其前驱结点的指针域。所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。其实是用空间来换时间。
与单链表相比,优势在于可以从任何结点开始任意向前向后双向访问,可以在当前结点前面或者后面插入,可以删除前趋和后继(包括结点自己)。
存储结构:
typedef struct node
{
int data;
struct node *prior; //前驱指针
struct node *next; //后继指针
}node,*Linklist;
在结点p和结点p->next之间插入一个值为e的结点s,操作为:
s->prior = p; //把p赋值给s的前驱
s->next = p->next; //把p->next赋值给s的后继
p->next->prior = s; //把s赋值给p->next的前驱
p->next = s; //把s赋值给p的后继
删除一个结点p,操作为:
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);