数据结构(C语言描述) 表

1 线性表

定义
由零个或多个数据元素组成的有限序列。
说明:
(1)线性表是一个序列,即元素之间是由个先来后到的。
(2)如果元素存在多个,则第一个元素无前驱,最后一个元素无后继,其他元素都有且只有一个前驱和后继。
(3)线性表是有限个的。

用数学语言来进行定义,将线性表记为(a1,…,ai-1,ai+1,an),则表中ai-1领先于ai,ai领先于ai+1,称ai-1是ai的直接前驱元素,ai+1是ai的直接后继元素。
在这里插入图片描述
线性表元素的个数n(n>=0)定义为线性表的长度,当n=0时,称为空表。

2 抽象数据类型

2.1 数据类型
原子类型:整型、浮点型、字符型等
结构体类型:由若干个类型组合而成,是可以再分解的。例如:
在这里插入图片描述
抽象是指取出事务具有的普遍性的本质。抽象数据类型可以是编程者在设计软件程序时自己定义的数据类型。

ADT 抽象数据类型名{

数据对象:<数据对象的定义>

数据关系:<数据关系的定义>

基本操作:<基本操作的定义>

}ADT 抽象数据类型名

数据对象:{a1,a2,…,an},任意数据元素的集合。
数据关系:第一个元素无前驱,最后一个元素无后继,其他元素都有且只有一个前驱和后继。
基本操作:抽象数据类型List的7个基本运算和结构初始化运算的接口如下:

List ListInit(int size)/*表结构初始化*/
int ListEmpty(List L)/*测试表L是否为空*/
int ListLength(List L);//表L的长度
ListItem ListRetrieve(int k,List L);
int ListLocate(ListItem x, List L);/*元素x在表L中的位置*/
void ListInsert(int k,ListItem x,List L);/*在表L的位置k之后插入元素x*/
ListItem ListDelete(int k,List L);/*从表L中删除位置k处的元素*/
void PrintList(List L);/*按位置次序输出表L中元素*/

这里主要讨论元素的插入和删除运算的实现。
表元素插入运算ListInsert(k,x,L):

void ListInsert(int k,ListItem x,List L)
{
    
    
if(k<0 || k>L->n) exit(1);
for(int i=L->n-1;i>=k;i--) L->table[i+1]=L->table[i];
L->table[k]=x;
l->n++;
}

分析:
(1)在第4行将表L位于k+1,k+2,……,n处的元素分别移到位置k+2,k+3,……,n+1处,然后早第5行将新元素插入位置k+1处。
(2)算法中元素后移的方向,必须从表中最后一个位置开始后移,直至将位子k+1处的元素后移。
(3)算法的时间复杂性,平均时间为o(n)。

表元素删除运算ListDelete(k,L):

 ListDelete(int k,List L)
 {
    
    
if(k<1 || k>L->n) exit(1);
 ListItem x=L->table[k-1];
 for(int i=k;i<L->n;i++) L->table[i-1]=L->table[i];
 L->n--;
 return x;
}

分析:
(1)通过将表L位于k+1,k+2,……,n处的元素移到位置k,k+1,……,n-1来删除原来位置k处的元素。
(2)最好情况下需要O(1)时间,而在最坏情况下需要O(n)时间。
删除运算所需的平均时间为O(n)。

3 用指针实现表

核心:让每个单元包含一个元素和一个指针,其中指针指向表中下一个元素所在的单元。这就是单链接表,简称单链表或链表。单链表的逻辑结构如下图。
在这里插入图片描述

3.1 单链表的结点结构说明

编写程序:

typedef struct node *link;//表节点指针类型
type struct node{
    
    
 ListItem element;//表元素
 link next;//指向下一结点的指针
}Node;
link NewNode()
{
    
    
return (link)malloc(sizeof(Node));
}

分析:
(1)ListItem表示用户指定的元素类型。其数据成员element存储表中元素。
(2)next是指向表中下一个元素的指针。函数NewNode()用于产生一个新结点。
(3)定义用指针实现表的结构List如下。

typedef struct llist *List;//单链表指针类型
typedef struct llsit{
    
    
	link first,//链表首指针
	curr,//链表当前结点指针
	last;//链表尾指针
}List;

分析:
(1)表结构List的数据成员first是指向表中第1个元素的指针。
(2)当表为空表时first指针是空指针。

3.2 函数ListInit()

编写程序:
使用“List ListInit(int size)//表结构初始化”,创建一个空表。

List ListInit()
{
    
    
List L=(List)malloc(sizeof *L);
L->first=0;
return L;
}

3.3 函数ListEmpty(L)

函数ListEmpty(L)测试当前表L是否为空,只要看表首指针first是否为空指针。
编写程序:

int ListEmpty(List L)/*测试表L是否为空*/
{
    
    
return L->first==0;
}

3.4 函数ListLength(L)

函数ListLength(L)对表L进行线性扫描计算表的长度。
编写程序:

int ListLength(List L){
    
    //表L的长度
	int len=0;
	link p=L->first;
	whiule(p){
    
    
		len++;
		p=p->next;
}
	return len;
}

算法ListLength(L)需要O(n)计算时间。

3.5 函数ListRetrieve(k,L)

编写程序:

ListItem ListRetrieve(int k,List L)
{
    
    
	if(k<1) exit(1);
	link p=L->first;
	int i=1;
	while(i<k&& p){
    
    
		p=p->next;
		i++;
	}
	return p->element;
}

函数ListRetrieve(k,L)从表首开始逐个元素向后线性扫描,直至找到表L中第k个元素。算法ListRetrieve(k,L)需要O(k)计算时间。

3.6 函数ListLocate(x,L)

编写程序:

int ListLocate(ListItrm x,List L)
{
    
    
	int i=1;
	link p=L->first;
	while(p&& p->element!=x){
    
    
		p=p->next;
		i++;
		}
		return p?1:0;
}

从表首开始逐个元素向后线性扫描,直至找到表L中元素x。在最坏情况下,算法ListLocate(x,L)需要O(n)需要计算时间。

3.7 单链表L插入元素

在单链表L中位置k处插入1个元素x的算法ListInsert(k,x,L)可实现如下
编写程序:

void ListInsert(int k,ListItem x,List L)
{
    
    
	if(k<0) exit(1);
	 link p=L->first;
	 for(int i=1;i<k&&p;i++) p=p->next;
	 link y=NewNode();
	 y->element=x;
	 if(k){
    
    y->next=p->next;p->next=y;}
	 else{
    
    y->next=L->first;L->first=y;}
}

分析:
首先扫描表找到插入位置k处的结点p,然后建立一个存储待插入元素x的新结点y,最后将结点y插入到结点p之后。
算法ListInsert(k,x,L)所需时间为O(k)。

3.8 在单链表删除元素

算法ListDelete(k.L)处理以下3种情况:
一,k<1或链表为空;二,删除的是表首元素,即k=1;三,删除非表首元素,即k>1。
遇到情况一,则表中不存在第k个元素,给出越界信息;遇到情况二,则直接修改首指针first,删除表首元素;三,则先找到表中第k-1元素所在结点q,然后修改结点q的指针域,删除第k个元素所在的结点p,如下图
在这里插入图片描述
编写程序:

ListItem ListDelete(int k,List L)
{
    
    
	if(k<1 || !L->first) exit(1);
	link p=L->next;
	if(k==1) L->first=p->next;
	else{
    
    
	link p=L->first;
	for(int i=1;i<k-1 && q;i++) q=q->next;
	p=q->next;
	q->next=p->next;
	}
	ListItem x=p->element;
	free(p);
	return x;
}

算法ListDelete(k,L)所需计算时间为O(k)。

3.9 函数PrintList(List L)

编写程序:

void PrintList(List L)
{
    
    
	for(link p=L->first;p;p=p->next) ItemShow(p->element);
}

分析:算法PrintList输出表L中所有元素。

4 用间接寻址方法实现表

间接寻址方法试将数组和指针结合起来实现表的一种方法,它将数组中原来存储元素的地方改为存储指向元素的指针。
在这里插入图片描述

4.1 用间接寻址方法实现表的结构

编写程序:

typedef struct indlist *List;//表指针类型
typedef struct indlist;//表结构类型
	int n,//表长
	curr;//当前位置
	int maxsize;//数组上界
	addr *table;//存储表元素指针的数组
}Indlist;

分析:
addr是表元素指针类型,已经在ListItem中定义。n为表长,maxsize是指针数组的最大长度,table是指向表中元素的指针数组。

4.1 NewNode()产生一个新节点

编写程序:

addr NewNode()
{
    
    
return (addr)malloc(sizeof(addr));
}

4.3 函数ListInit(size)

编写程序:

List ListInit(int size)
{
    
    
	List L=(List)malloc(sizeof *L);
	L->n=0;
	L->maxsize=size;
	L->table=(addr *)maddloc(size*sizeof(addr));
	return L;
}

分析:创建一个最大长度为size的空表

4.4 表运算ListEmpty(L)和ListLength(L)

编写程序:

int ListEmpty(List L)
{
    
    
 	return L->n==0;
}
int ListLength(List L){
    
    
	return L->n;
}

分析:表运算ListEmpty(L)和ListLength(L)只需O(1)计算时间。

4.5 表运算ListRetrieve(k,L)和 ListLocate(x,L)

ListRetrieve(k,L)
编写程序:

ListItem ListRerieve(ListItem x.List L)
{
    
    
	for(int i=0;i<L->n;i++)
	if(*L->table[i]==x) return ++i;
	return 0;
}

分析:
ListRetrieve(k,L)返回表L的位置k处的元素。表L中没有位置k时,给出错误信息,算法ListRetrieve(k,L)只需O(1)计算时间。
ListLocate(x,L)
编写程序:

int ListLocatte(ListItem x,List L)
{
    
    
	for(int i=0;i<L->n;i++)
		if(*L->table[i]==x) return ++i;
	return 0;
}

分析:
ListLocate(x,L)返回元素x在表L中的位置,当元素x不在表L中是返回0。算法ListLocate(x,L)在最坏情况下需要O(n)计算时间。

4.6 用间接寻址方法实现表元素的插入运算

表元素插入运算ListInsert(k,x,L)实现如下。
编写程序:

void ListInsert(int k,ListItem x,List L)
{
    
    
	if(k<0 || k>L->n) exit(1);
	for(int i=L->n-1;i>=k;i--) L->table[i+1]=L->table[i];
	L->table[k]=NewNode();
	*L->table[k]=x;
	L->n++;
}

分析:
(1)核心:不实际移动元素,而只移动指向元素的指针。
(2)算法ListInsert(l,x,L)将位于k+1,k+2,……,n处的元素指针分别移动位置k+2,k+3,……,n+1处,然后将指向新元素x的指针插入位置k+1处。
(3)该算法计算时间为O(k)。但该算法比用数组实现表的插入算法快的多。

4.7 用间接寻址方法实现表元素删除运算

表元素删除运算ListDelete(k,L)实现如下。
编写程序:

ListItem ListDelete(int k,List L)
{
    
    
	if(k>1 || k>L->n) exit(1);
	addr p=L->table[k-1];
	ListItem x=*p;
	for(int i=k;i<l->n;i++) L->table[i-1]=L->table[i];
	L->n--;
	free(p);
	return x;
}

分析:
(1)核心:不实际移动元素,而只移动指向元素的指针。
(2)算法ListInsert(l,x,L)将位于k+1,k+2,……,n处的元素指针移到位置k,k+1,……,n-1处来删除原来位置k处的元素。
(3)该算法计算时间为O(k)。但该算法比用数组实现表的插入算法快的多。

4.8 用间接寻址方法输出表所有元素

输出表所有元素的函数PrintList(L)

编写程序:

void PrintList(List L)
{
    
    
	for(int i=0;i<L->n;i++)ItemShow(*L->table[i]);
}

5 实现游标对指针的模拟

游标是数值中指示数组单元地址的下标值,属于整数类型。用数组和指针相结合,并用游标,模拟指针的方法来实现表。

5.1 数组单元类型Snode定义

typedef struct snode *link;//结点指针类型
typedef struct snode{
    
    
	ListItem element;//表中元素
	int next;//模拟指针的游标
}

分析:
(1)element域存储表中元素;next是用于,模拟指针的游标,它指示表中下一个元素再数组中的存储地址。
(2)用游标模拟指针可方便实现单链表中的各种运算。

5.2 定义模拟空间结构类型Space

目的:实现游标对指针的模拟,必须先设计模拟内存管理的结点空间分配与释放运算、模拟C语言的函数malloc和free。

typedef struct space *Space;
typedef struct space{
    
    
	int num,//可用数组空间大小
	first;//可用数组单元小标
	link node;//可用空间数组
}Simul;

分析:
(1)数据成员num表示可用数组空间大小
(2)node[0:num]是供分配的可用数组,初始时所有单元均可分配

5.3 函数SpaceInit(int max)

Space SpaceInit(int max)
{
    
    
	Space s=(Space)malloc(sizeof*s);
	s->num=max;
	s->node=(link)malloc(max*(sizeof*s->node));
	for(int i=0;i<max-1;i++) s->node[i].next=i+1;
	s->node[max-1].next=-1;
	s->first=0;
	return s;
}

分析:函数SpaceInit(max)创建一个可用数组空间最大长度为max的模拟空间结构。所需的计算时间O(num)。

5.4 函数SpaceAllocate(s)

int SpaceAllocate(Space s)
{
    
    
	int i=s->first;
	s->first=s->node[i].next;
	return i;
}

分析:从s的当前可用数组空间中分配一个数组单元的函数SpaceAllocate(s)。所需的计算时间为O(1)。

5.5 函数SpaceDeallocate(i,s)

void SpaceDeallocate(int i,Space s){
    
    
	s->node[i].next=s->first;
	s->first=i;
}

分析:释放s的数组单元i的函数SpaceDeallocate(i,s),所需的计算时间为O(1)。

6 双可用空间表方法实现游标对指针的模拟

用可用空间表表示当前可用数组空间。其中,第一个可用空间表中含有所有未用过的可用数组单元;第2个可用空间表中含有所有至少被用过1次且已被释放的可用数组单元。SpaceDeallocate释放的所有单元均链如第2个可用空间表中备用。SpaceAllocate在分配1个可用数组单元时,总是先从第2个可用空间表中获取可用数据单元。

6.1 定义双模拟空间结构类型

在双可用空间表中用first指向第一个可用空间表的表首可用数组单元,用first2指向第2个可用空间表的表首可用数组单元。

typedef struct dspace *Space;//双模拟空间指针类型
typedef struct dspace{
    
    //双模拟空间结构类型
	int num,//可用数组空间大小
	first1,//第1个可用空间表的表首可用数组单元下标
	first2,//第2个可用空间表的表首可用数组单元下标
	link node;//可用空间数组
}Dspace;

6.2 函数SpaceInit(max)

创建初始可用数组空间的函数得到简化如下:

Space SpaceInit(int max)
{
    
    
	Space s=(Space)malloc(sizeof *s);
	s->num=max;
	s->node=(link)malloc(max*(sizeof *s->node));
	s->first1=0;
	s->first2=-1;
	return s;
}

6.3 函数SpaceAllocate(s)

从当前可用数组空间中分配一个数组单元SpaceAllocate(s)相应修改如下。

int SpaceAllocate(Space s)
{
    
    
	if(s->first2==-1) return s->first1++;
	int i=s->first2;
	s->first2=s->node[i].next;
	return i;
}

6.4 函数函数SpaceDeallocate(i,s)

释放数组单元i的函数后SpaceDeallocate(i,s)也进行相应修改如下。

void SpaceDeallcate(int i,Space s)
{
    
    
	s->node[i].next=s->first2;
	s->first2=i;
}

7 用游标实现表

7.1 用游标实现的表结构

表结构List

typedef struct slist *List;//游标表指针类型
typedef struct slist{
    
    //游标表结构
	int first,//表首结点游标
		Space s;
}Slist;

分析:
List的数据成员first是表首结点游标;s表示可用数组空间。

7.2 函数ListInit(size)

List ListInit(int size)
{
    
    
	List L=(List)malloc(sizeof *L);
	L->s=SpaceInit(size);
	L->first=-1;
	return L;
}

分析:函数ListInit(size)申请大小为size的模拟空间,并置表首结点游标first为-1,创建一个空表。

7.3 函数ListLength(L)

int ListLength(List L)
{
    
    
	int i=L->first,len=0;
	while(i!=-1){
    
    
		len++;
		i=L->s->node[i].next;
	}
	return len;
}

分析:函数ListLength(L)对表L进行线性扫描来计算表的长度。

7.4 函数ListRetrieve(k,L)

ListItem ListRetrieve(int k,List L)
{
    
    
	int p,i=1;
	if(k<1) exist(1);
	p=L->first;
	while(i<k && p!=-1){
    
    
		p=L->s->node[p].next;
		i++;
}
	return L->s->node[p].element;
}

7.5 函数ListLocate(x,L)

int ListLocate(ListItem x,List L)
{
    
    
	int p,i=1;
	p=L->first;
	while(p!=-1 && L->s->ndoe[p].element!=x){
    
    
		p=L->s->node[p].next;
		i++;
}
	return ((p>=0)?i:0);
}

分析:从表首开始逐个元素向后进行线性扫描,直到扎到表L中元素x。在最坏情况下,它需要O(n)计算时间。

7.6 插入表元素

思路:要在当前表的第k个元素之后插入一个新元素x,应先找到插入位置,即当前表的第k个元素所处的位置;然后从可用数组空间中为新元素分配一个存储单元,并将由此产生的新元素插入表的第k个元素之后。

void ListInsert(int k,ListItem x,List L)
{
    
    
	if(k<0) exit(1);
	int p-L->List;
	for(int i=1;i<k && p!=-1;i++) p=L->s->node[p].next;
	int y=SpaceAllocate(L->s);
	L->s->node[y].element=x;
	if(k){
    
    
		L->s->node[y].next=L->s->node[p].next;
		L->s->node[p].element=y;
	}
	else{
    
    
		L->s->node[y].next=L->first;
		L->first=y;
		}
}

分析:以上结构未使用表首哨兵单元,所以碧玺单独处理在表的第一个位置插入的情形。算法ListInsert(k,x,L)的计算时间为O(k)。

7.7 删除表元素

思路:先找到当前表的第k个元素所处的位置;然后将存储该元素的那个单元摘除,并把该单元释放到可用数组空间中备用。

ListItem ListDelete(int k,List L)
{
    
    
	if(k<1 || L->first==-1) exit(1);
	int p=L->first;
	if(k==1) L->first=L->s->node[p].next;
	else{
    
    
		int q=p;
		for(int i=1;i<k-1 && q!=-1;i++) q=L->s->node[q].next;
		p=L->s->node[q].next;
		L->s->node[q].next=L->s->node[p].next;
}
	ListItem x=L->s->ndoe[p].element;
	SpaceDeallocate(p,L->s);
	return x;
}

分析:算法ListDelete(k,L)所需计算时间为O(k)。

7.8 函数PrintList(L)

void PrintList(List L)
{
    
    
	for(int p=L->first;p!=-1;p=L->s->ndoe[p].next)
		ItemShow(L->s->ndoe[p].element);
}

分析:输出表中所有元素的函数PrintList(L)。

8 循环链表

在用指针实现表时,表中最后一个元素所在单元的指针为空指针,如果将这个空指针改为指向表首单元的指针,就使整个链表形成一个环。这种首尾相接的链表就称为循环链表。
在循环链表中,从任意一个单元出发都可以找到表中其他单元。
一个单链的循环链表,简称单循环链表。

8.1 单循环链表的结构

typedef struct clsit *List;//单循环链表指针类型
typedef struct clsit{
    
    //但循环链表结构
	int n;//表长
	link last;//链表尾指针
}Clist;

8.2 函数ListInit()

创建一个空表。

List listInit()
{
    
    
	List L=(List)malloc(sizeof *L);
	link y=NewNode();
	y->next=y;
	L->last=y;
	L->n=0;
	return L;

8.3 函数ListRetrieve(k,L)

从表中第1个元素开始逐个向后扫描,直至找到第k个元素。

ListItem listRetrieve(int l,List L)
{
    
    
	int i=1;
	if(k<1 || k>L->n) exit(1);
	link p=L->last->next->next;
	while(i<k){
    
    
		p=p->next;
		i++;
}
	return p->element;
}

分析:函数ListRetrieve(k,L)需要O(k)计算时间。

8.4 函数ListLocate(x,L)

从表中第一个元素逐个想后线性扫描,直至找打元素x。在最坏情况下,算法ListLocate(x,L)需要O(n)计算时间。

int ListLocate(ListItem x,List L)
{
    
    
	int i=1;
	link p=L->next->next;
	L->last->next->element=x;
	while(p->element!=x)
		p=p->next;
		i++;
}
	return ((p==L->last->next)?0:i);
}

8.5 函数ListInsert(k,x,L)

在训话链表中插入一个新元素的算法与单链表类似。采用表首哨兵单元,简化了算法对表首插入的边界情形的处理。

void ListInsert(int k,ListItem x,List L)
{
    
    
	if(k<0 || k>L->n) exit(1);
	link p=L->next->next;
	link y=NewNode();
	y->element=x;
	y->next=p->next;
	p->next=y;
	if(k==L->n) L->last=y;
	L->n++;
}

算法ListInsert(k,x,L)所需计算时间为O(k)。

8.6 函数ListDelete(k,L)

在循环链表中刷除第k个元素的算法也与单链表的情形类似。同样,表首哨兵单元的采用,简化了算法对删除表中第1个元素的边界情形的处理。

ListItem ListDelete(int k,List L)
{
    
    
	if(k<1||k>L->n) exit(1);
	link q=L->last->next;
	for(int i=0;i<k-1;i++) q=q->next;
	link p=q->next;
	q->next=p->next;
	if(k==L->n) L->last=q;
	ListItem x=p->element;
	free(p);
	L->n--;
	return x;
}

8.7 函数PrintList(L)

输出表中所有元素。

void PrintList(List L)
{
    
    
	for(link p=L->last->next->next;p!=L->last->next;p=p->next)
ItemShow(p->element);
}

9 双链表

如果希望快速确定表中任一元素的前驱和后继所在的结点,可用在链表的每个结点中设置两个指针,一个指向后继节点,另一个指向前驱结点,形成双向链表,简称为双链表。
在这里插入图片描述

9.1 双链表结点类型定义

typedef struct node *link;//双链表结点指针类型
typedef struct node{
    
    //双链表结点类型
	ListItem elemnet;//表中元素
	link left,//链表做结点指针
	right;//链表右结点指针
}Node;

分析:数据成员element存储表中元素;left是指向前一结点的指针;right是指向后一结点的指针。

9.2 双链表实现表的结构类型定义

typedef struct dlist *List;//双链表结点指针类型
typedef struct dlist{
    
    //双链表结点类型
	int n;//表长
	link leftEnd,//链表做结点指针
	rightEnd;//链表右结点指针
}Dlist;

分析:数据成员n存储表的长度;leftEnd是指向表首的指针;rightEnd是指向表尾的指针。
拓展:双链表也可以有循环表。用一个表首哨兵header将双联表表首尾想接,即将表首哨兵节点中的left指针指向表尾,并将表尾结点的right指针指向表首哨兵结点,构成双向循环链表。
在这里插入图片描述

9.3 双向循环链表实现表的结构类型定义

typedef struct dlist *List;//循环双链表指针类型
typedef struct dlist;//循环双链表结构
	int n;//表长
	link head,//链表表首指针
	curr;//链表当前结点指针
}Dlist;

head是指向表首哨兵结点的指针。

9.4 函数ListInit()

创建一个仅由表首哨兵结点组成的空双向循环链表。

List ListInit()
{
    
    
	List L=(List)malloc(sizeof *L);
	link y=NewNode();
	y->left=y;
	y->right=y;
	L->header=y;
	L->n=0;
	return L;
}

9.5 函数ListRetrieve(k,L)

从表L中第一个元素开始逐个向后扫描,直至找到第k个元素。

ListItem ListRetrieve(int k,List L)
{
    
    
	if(k<1 || k>L->n) exit(1);//越界
	if(k==L->n) return L->header->left->element;
	link p=L->header->right;
	for(int i=1;i<k;i++) p=p->right;
	return p->element;
}

由于双向循环链表的特点,计算时间为O(1)。

9.6 函数ListLocate(x,L)

从表中第1个元素开始逐个向后进行线性扫描,直至找到元素x。在最坏情况下,算法ListLocate(x,L)需要O(n)时间。

int ListLocate(ListItem x,List L)
{
    
    
	int i=1;
	link p=L->header->right;
	L->header->element=x;
	while(p->element!=x)
		p=p->right;
		i++;
}
	return ((p==L->header)?0:i);
}

9.7 双向循环链表插入运算

修改向前和向后两个方向的指针
在双向循环链表的第k个元素之后插入一个新元素x。

void ListInsert(int k,ListItem x,List L)
{
    
    
	if(k<0 || k>L->n) exit(1);
	link p=L->header;
	if(k==L->n) p=L->header->left;
	else for(int i=1;i<=k;i++) p=p->right;
	link y=NewNode();
	y->element=x;
	y->left=p;
	y->right=p->right;
	p->right->left=y;
	p->right=y;
	L->n++;
}

由双向循环链表的特点,计算时间为O(1)。
在这里插入图片描述

9.8 双向循环链表删除运算

删除双向循环链表中第k个元素的算法实现。

List ListDelete(int k,List L)
{
    
    
	if(k<0 || k>L->n) exit(1);
	link p=L->header;
	if(k==L->n) p=L->header->left;
	else for(int i=1;i<=k;i++) p=p->right;
	p->left->right=p->right;
	p->right->left=p->left;
	ListItem x=p->element;
	free(p);
	L->n--;
	return x;
}

分析:算法ListDelete(k,L)所需计算时间O(k)。
在这里插入图片描述

9.9 输出双向循环表中所有元素

用游标来模拟指针,实现用游标表示的双向链表和循环链表。
输出双向循环链表中所有元素的。

void PrintList(List L)
{
    
    
	for(link p=L->header->right;p!=L->header;p=p->right)
		ItemShow(p->element);
}

10 表的搜索游标

在对表进行各种操作时,常需要对表进行顺序扫描为了使这种顺序扫描具有通用性,可以将与之相关的运算定义为抽象数据类型表的基本运算,常用的有如下几种。
(1)IterInit(L):初始化搜索游标。
(2)IterNext(L):当前搜索游标的下一个位置。若当前搜索游标在表尾,则下一个位置为表首。
(3) CurrItem(L):当前搜索游标处的表元素。
(4)InsertCurr(x, L):在当前搜索游标处插入元素x。
(5) DeleteCurr(L) :删除当前搜索游标处的元素。

10.1 用数组实现表的搜索游标

在用数组实现的表结构中增加一个数据成员curr,用于记录当前搜索游标的值。

typedef struct alist *List;//但链表指针类型
typedef struct alist{
    
    
	int n,//表长
	curr;//当前位置
	int maxsize;//数组上界
	ListItem *table;//存储表元素的数组
}Alist;

10.2 函数IterInit(L)

将表L的搜索游标初始化为表首元素的位置。

void IterInit(List L)
{
    
    
	L->curr=0;
}

10.3 函数IterNext(L)

将表L的搜索游标从当前位置移向下一个位置。

void IterNext(List L)
{
    
    
	L->curr=(L->curr+1)%L->n;
}

10.4 函数CurrItem(L)

返回表L的当前搜索游标处的元素。

ListItem *CurrItem(List L)
{
    
    
	return &L->table[L->curr];
}

10.5 函数InsertCurr(x,L)

当前搜索游标处插入元素x。

void InsertCurr(ListItem x,List L)
{
    
    
	ListInsert(L->curr,x,L);
}

10.6 函数DeleteCurr(L)

删除并返回表L的当前搜索游标处的元素。

ListItem DeleteCurr(List L)
{
    
    
	ListItem x=ListDelete(L->curr+1,L);
	L->curr=L->curr%L->n;
	return x;
}

11 单循环链表的搜索游标

11.1 增加数据成员curr

在单循环链表结构中增加一个数据成员curr,用于记录当前搜索游标指针。

typedef struct clist *List;//单循环链表指针类型
typedef struct clist{
    
    //单循环链表结构
	int n;//表长
	link last,//链表表尾指针
	curr;//链表当前结点指针
}Clist;

11.2 函数IterInit()

将表L的搜索游标初始化为指向表首哨兵结点的指针。

void IterInit(List L)
{
    
    
	L->curr=L->last->next;
}

11.3 函数IterNext(L)

将搜索游标指针下移。

void IterNext(List L)
{
    
    
	L->curr=L->curr->next;
	if(L->curr==L->last) L->curr=L->curr->next;
}

11.4 函数CurrItem(L)

返回游标处的元素

ListItem *CurrItem(List L)
{
    
    
	if(L->n==0) return 0;
	else return &L->curr->next->element;
}

11.5 函数InsertCurr(x,L)

由于curr指针已指向当前结点的前一结点,所以直接在curr指针处插入新结点即可,不用调用ListInsert(k,x,L)。

void InsertCurr(ListItem x,List L)
{
    
    
	link y=NewNode();
	y->element=x;
	y->next=L->curr->next;
	L->curr->next=y;
	if(L->curr==L->last) L->last=y;
	L->n++;
}

11.6 函数DeleteCurr(L)

由于curr指针已指向当前结点的前一结点,所以可直接修改指针删除当前结点,而不必调用ListDelete(k,L)。

ListItem DeleteCurr(List L)
{
    
    
	link q=L->curr;
	link p=q->next;
	q->next=p->next;
	if(p==L->last) {
    
    L->last=q;L->curr=L->curr->next;}
	ListItem x=p->element;
	free(p);
	L->n--;
	return x;
}

猜你喜欢

转载自blog.csdn.net/qq_45059457/article/details/114338548