第3章 线性表的链式存储
3.1链式存储
数据结构的存储方式必须体现它的逻辑关系 。在链式存储方式下,实现中除存放一个结点的信息外,还需附设指针,用指针体现结点之间的逻辑关系。如果一个结点有多个后继或多个前驱,那么可以附设相应个数的指针,一个结点附设的指针指向的是这个结点的某个前驱或后继。
3.2单链表
结点一般含有两个域,一个是存放数据信息的info域,另一个是指向该结点的后继结点的存放地址的指针域next。一个单链表必须有一个首指针指向单链表中的第一个结点。
|
ADT link_list{
|
|
数据集合K:K={k1, k2,…, kn},n≥0,K中的元素是datatype
|
|
类型数据关系R:R={r}
|
|
r={ <ki, ki+1>| i=1,2,…,n-1}
|
| 操作集合: |
|
(1) node *init_link_list() 建立一个空的单链表
|
|
(2) void print_link_list(node *head) 输出单链表中各个结点的值
|
|
(3) node *insert_in_front_link_list(node *head,datatype x) 插入一个值为x的结点作为单链表的第一个结点
|
|
(4) node *find_num_link_list(node *head,datatype x) 在单链表中查找一个值为x的结点
|
|
(5) node *find_pos_link_list(node *head,int i) 在单链表中查找第i个结点
|
|
(6) node *insert_x_after_y(node *head,datatype x,datatype y) 在单链表中值为y的结点后插入一个值为x的新结点
|
|
(7) node *insert_x_after_i(node *head,datatype x,int i) 在单链表中第i个结点后插入一个值为x的新结点
|
|
(8) node *delete_num_link_list(node *head,datatype x) 在单链表中删除一个值为x的结点
|
|
(9) node *delete_pos_link_list(node *head,int i) 在单链表中删除第i个结点
|
|
}ADT link_list;
|
|
// slnklist.h
|
| |
|
typedef int datatype;
|
|
typedef struct link_node{
|
|
datatype info;
|
|
struct link_node *next;
|
|
}node;
|
|
// 建立一个空的单链表
|
| |
|
node *init_link_list()
|
| { |
|
return NULL;
|
| } |
|
// slnkprin.c
|
| |
|
void print_link_list(node *head)
|
| { |
|
node *p;
|
| |
|
p=head;
|
|
if(!p)
|
|
printf("\nslinklist is empty!");
|
|
else
|
|
{
|
|
printf("\neach value of node is:\n");
|
|
while(p)
|
|
{
|
|
printf("%5d",p->info);
|
|
p=p->next;
|
|
}
|
|
}
|
| } |
|
// 查找一个值为x的结点
|
| |
|
node *find_num_link_list(node *head,datatype x)
|
| { |
|
node *p;
|
| |
|
p=head;
|
|
while(p&&p->info!=x)
|
|
p=p->next;
|
|
|
|
return p;
|
|
}
|
|
// 查找第i个结点
|
| |
|
node *find_pos_link_list(node *head,int i)
|
| { |
|
int j=1;
|
|
node *p=head;
|
| |
|
if(i<1)
|
|
{
|
|
printf("\nError!\n");
|
|
exit(1);
|
|
}
|
|
while(p&&i!=j)
|
|
{
|
|
p=p->next;
|
|
j++;
|
|
}
|
|
return p;
|
|
}
|
|
// 插入一个值为x的结点作为单链表的第一个结点
|
| |
|
node *insert_in_front_link_list(node *head,datatype x)
|
| { |
|
node *p;
|
|
p=(node*)malloc(sizeof(node)); /*分配空间*/
|
|
p->info=x; /*设置新结点的值*/
|
|
p->next=head; /*插入(1)*/
|
|
head=p; /*插入(2)*/
|
|
return head;
|
| } |
|
// 在单链表中第i个结点后插入一个值为x的新结点
|
| |
|
node *insert_x_after_i(node *head,datatype x,int i)
|
| { |
|
node *p,*q;
|
| |
|
q=find_pos_link_list(head,i);/*查找第i个结点*/
|
|
if(!q)
|
|
{
|
|
printf("\ncan't find node %d, can't insert!\n",i);
|
|
exit(1);
|
|
}
|
|
p=(node*)malloc(sizeof(node));/*分配空间*/
|
|
p->info=x; /*设置新结点*/
|
|
p->next=q->next; /*插入(1)*/
|
|
q->next=p; /*插入(2)*/
|
| |
|
return head;
|
| } |
|
// 删除一个值为x的新结点
|
| |
|
node *delete_num_link_list(node *head,datatype x)
|
| { |
|
node *pre=NULL,*p;
|
|
if(!head)
|
|
{
|
|
printf("\nthe slinklist is empty!\n");
|
|
return head;
|
|
}
|
|
p=head;
|
|
while(p&&p->info!=x)/*没有找到并且没有找完*/
|
|
{
|
|
pre=p;p=p->next;}/*pre指向p的前驱结点*/
|
|
if(!pre&&p->info==x)/*要删除的是第一个结点*/
|
|
head=head->next;/*删除(1)*/
|
|
else
|
|
pre->next=p->next;
|
|
free(p);
|
|
|
|
return head;
|
| } |
3.3带头结点单链表
一般的单链表中,第一个结点由head指示,而在带头结点单链表中,head指示的是所谓的头结点,它不是存储数据结构中的实际结点,第一个实际的结点是head->next指示的。在带头结点单链表的操作实现时要注意这一点。
|
ADT hlink_list
|
| { |
|
数据集合K:K={k1, k2,…, kn},n≥0,K中的元素是datatype类型
|
|
数据关系R:R={r}
|
|
r={ <ki, ki+1>| i=1,2,…,n-1}
|
| 操作集合: |
|
(1) node *init_hlink_list() 建立一个空的带头结点的单链表
|
|
(2) void print_hlink_list(node *head) 输出带头结点单链表中各个结点的值
|
|
(3) node *find_num_hlink_list(node *head,datatype x) 在带头结点单链表中查找一个值为x的结点
|
|
(4) node *find_pos_hlink_list(node *head,int i) 在带头结点单链表中查找第i个结点
|
|
(5) node *insert_in_front_hlink_list(node *head,datatype x) 插入一个值为x的结点作为带头结点单链表的第一个结点
|
|
(6) node *insert_x_after_y(node *head,datatype x,datatype y) 在带头结点单链表中值为y的结点后插入一个值为x的新结点
|
|
(7) node *insert_x_after_i(node *head,datatype x,int i) 在带头结点单链表中第i个结点后插入一个值为x的新结点
|
|
(8) node *delete_num_hlink_list(node *head,datatype x) 在带头结点单链表中删除一个值为x的结点
|
|
(9) node *delete_pos_hlink_list(node *head,int i) 在带头结点单链表中删除第i个结点
|
|
}ADT hlink_list;
|
|
// 建立一个空的带头结点单链表
|
| |
|
node *init_hlink_list()
|
| { |
|
node *head;
|
| |
|
head=(node*)malloc(sizeof(node));
|
|
head->next=NULL;
|
| |
|
return head;
|
| } |
|
// 输出带头结点单链表中各个结点的值
|
| |
|
void print_hlink_list(node *head)
|
| { |
|
node *p;
|
| |
|
p=head->next;/*从第一个(实际)结点开始*/
|
|
if(!p)
|
|
printf("\n带头结点单链表是空的!");
|
|
else
|
|
{
|
|
printf("\n带头结点的单链表各个结点的值为:\n");
|
|
while(p)
|
|
{
|
|
printf("%5d",p->info);
|
|
p=p->next;
|
|
}
|
|
}
|
| } |
|
// 在带头结点单链表中查找一个值为x的结点
|
| |
|
node *find_num_hlink_list(node *head,datatype x)
|
| { |
|
node *p;
|
| |
|
p=head->next;/*从第一个(实际)结点开始*/
|
|
while(p&&p->info!=x)
|
|
p=p->next;
|
|
|
|
return p;
|
| } |
|
// 在带头结点单链表中查找第i个结点
|
| |
|
node *find_pos_hlink_list(node *head,int i)
|
| { |
|
int j=0;
|
|
node *p=head;
|
| |
|
if(i<0)
|
|
{
|
|
printf("\n带头结点的单链表中不存在第%d个结点!",i);
|
|
return NULL;
|
|
}
|
|
while(p&&i!=j)/*没有查找完并且还没有找到*/
|
|
{
|
|
p=p->next;
|
|
j++;/*继续向后(左)查找,计数器加1*/
|
|
}
|
| |
|
return p;/*返回结果,i=0时,p指示的是头结点*/
|
| } |
|
// 在带头结点单链表中值为y的结点后插入一个值为x的新结点
|
| |
|
node *insert_x_after_y(node *head,datatype x,datatype y)
|
| { |
|
node *p,*q;
|
| |
|
q=find_num_hlink_list(head,y);/*查找值为y的结点*/
|
|
if(!q)/*没有找到*/
|
|
{
|
|
printf("\n没有找到值为%d的结点,不能插入%d!",y,x);
|
|
return head;
|
|
}
|
|
p=(node*)malloc(sizeof(node));/*为准备插入的新结点分配空间*/
|
|
p->info=x;/*为新结点设置值x*/
|
|
p->next=q->next;/*插入(1)*/
|
|
q->next=p;/*插入(2)*/
|
| |
|
return head;
|
| } |
|
// 在带头结点单链表中删除一个值为x的结点
|
| |
|
node *delete_num_hlink_list(node *head,datatype x)
|
| { |
|
node *pre=head,*q;/*首先pre指向头结点*/
|
|
q=head->next;/*q从带头结点的第一个实际结点开始找值为x的结点*/
|
|
while(q&&q->info!=x)/*没有查找完并且还没有找到*/
|
|
{
|
|
pre=q;
|
|
q=q->next;
|
|
}/*继续查找,pre指向q的前驱*/
|
|
pre->next=q->next;/*删除*/
|
|
free(q);/*释放空间*/
|
| |
|
return head;
|
| } |
3.4循环单链表
对于一个循环单链表,若首指针为head,表中的某个结点p是最后一个结点的特征应该是p->next==head。
循环单链表的头文件和单链表的相同。
|
// 建立一个空的循环单链表
|
| |
|
node *init_clink_list()
|
|
{
|
|
return NULL;
|
|
}
|
|
// 输出循环单链表中各个结点的值
|
| |
|
void print_clink_list(node *head)
|
| { |
|
node *p;
|
| |
|
if(!head)
|
|
printf("\n循环单链表是空的!\n");
|
|
else
|
|
{
|
|
printf("\n循环单链表各个结点的值分别为:\n");
|
|
printf("%5d",head->info);/*输出非空表中第一个结点的值*/
|
|
p=head->next;/*p指向第一个结点的下一个结点*/
|
|
while(p!=head)/*没有回到第一个结点*/
|
|
{
|
|
printf("%5d",p->info);
|
|
p=p->next;
|
|
}
|
|
}
|
| } |
|
// 在循环单链表中第i个结点后插入一个值为x的新结点
|
| |
|
node *insert_x_after_i(node *head,datatype x,int i)
|
| { |
|
node *p,*q;
|
| |
|
q=find_pos_clink_list(head,i);/*查找第i个结点,q指向第i个结点*/
|
|
if(!q)/*没有找到,则不进行插入*/
|
|
printf("\n表中不存在第%d个结点,无法进行插入!\n",i);
|
|
else
|
|
{ /*找到了第i个结点,准备插入x*/
|
|
p=(node*)malloc(sizeof(node));/*分配空间*/
|
|
p->info=x;/*设置新结点的值*/
|
|
p->next=q->next;/*插入,修改指针(1)*/
|
|
q->next=p;/*插入,修改指针(2)*/
|
|
}
|
| |
|
return head;
|
| } |
|
// 在循环单链表中删除一个值为x的结点
|
| |
|
node *delete_num_clink_list(node *head,datatype x)
|
| { |
|
node *pre=NULL,*q;/*q用于查找值为x的结点,pre指向q的前驱结点*/
|
| |
|
if(!head)/*表为空,则无法做删除操作*/
|
|
{
|
|
printf("\n循环单链表为空,无法做删除操作!");
|
|
return NULL;
|
|
}
|
|
q=head;/*从第1个结点开始准备查找*/
|
|
while(q->next!=head&&q->info!=x)/*没有找遍整个表并且没有找到*/
|
|
{
|
|
pre=q;
|
|
q=q->next;/*pre为q的前驱,继续查找*/
|
|
}/*循环结束后,pre为q的前驱*/
|
|
if(q->info!=x)/*没找到*/
|
|
{
|
|
printf("没有找到值为%d的结点!",x);
|
|
}
|
|
else /*找到了,下面要删除q*/
|
|
{
|
|
pre->next=q->next;/*删除q指向的结点*/
|
|
free(q);/*释放空间*/
|
|
}
|
| |
|
return head;
|
| } |
3.5双链表
|
// 双链表的头文件
|
| |
|
typedef int datatype;
|
|
typedef struct dlink_node{
|
|
datatype info;
|
|
struct dlink_node *llink,*rlink;
|
|
}dnode;
|
|
// 输出双链表中各个结点的值
|
| |
|
void print_dlink_list(dnode *head)
|
| { |
|
dnode *p;
|
| |
|
p=head;
|
|
if(!p)
|
|
printf("\n双链表是空的!\n");
|
|
else
|
|
{
|
|
printf("\n双链表中各个结点的值为:\n");
|
|
while(p)
|
|
{
|
|
printf("%5d",p->info);
|
|
p=p->rlink;
|
|
}
|
|
}
|
| } |
|
// 查找双链表中第i个结点
|
| |
|
dnode *find_pos_dlink_list(dnode *head,int i)
|
| { |
|
int j=1;
|
|
dnode *p=head;
|
| |
|
if(i<1)
|
|
{
|
|
printf("\n第%d个结点不存在!\n",i);
|
|
return NULL;
|
|
}
|
|
while(p&&i!=j)/*没有找完整个表并且没有找到*/
|
|
{
|
|
p=p->rlink;j++;/*继续沿着右指针向后查找,计数器加1*/
|
|
}
|
|
if(!p)
|
|
{
|
|
printf("\n第%d个结点不存在!\n",i);
|
|
return NULL;
|
|
}
|
|
|
|
return p;
|
| } |
|
// 在双链表中第i个结点后插入一个值为x的新结点
|
| |
|
dnode *insert_x_after_i(dnode *head,datatype x,int i)
|
| { |
|
dnode *p,*q;
|
| |
|
p=(dnode*)malloc(sizeof(dnode));/*分配空间*/
|
|
p->info=x;/*设置新结点的值*/
|
|
if(i==0)/*在最前面插入一个值为x的新结点*/
|
|
{
|
|
p->llink=NULL;/*新插入的结点没有前驱*/
|
|
p->rlink=head;/*新插入的结点的后继是原来双链表中的第一个结点*/
|
|
head=p;/*新结点成为双链表的第一个结点*/
|
|
return head;
|
|
}
|
|
q=find_pos_dlink_list(head,i);/*查找第i个结点*/
|
|
if(!q)/*第i个结点不存在*/
|
|
{
|
|
printf("第%d个结点不存在,无法进行插入",i);
|
|
return head;
|
|
}
|
|
if(q->rlink==NULL)/*在最后一个结点后插入*/
|
|
{
|
|
p->rlink=q->rlink;/*即为NULL,新插入的结点没有后继。插入操作(1)*/
|
|
p->llink=q;/*插入操作(2)*/
|
|
q->rlink=p;/*插入操作(4)*/
|
|
}/*注意不能和下面的一般情况一样处理,这里如执行下面的(3)将出错!*/
|
|
else/*一般情况下的插入*/
|
|
{
|
|
p->rlink=q->rlink;/*插入操作(1)*/
|
|
p->llink=q;/*插入操作(2)*/
|
|
q->rlink->llink=p;/*插入操作(3)*/
|
|
q->rlink=p;/*插入操作(4)*/
|
|
}
|
| |
|
return head;
|
| } |
|
// 在双链表中删除一个值为x的结点
|
| |
|
dnode *delete_num_dlink_list(dnode *head,datatype x)
|
| { |
|
dnode *q;
|
| |
|
if(!head)/*双链表为空,无法进行删除操作*/
|
|
{
|
|
printf("双链表为空,无法进行删除操作");
|
|
return head;
|
|
}
|
|
q=head;
|
|
while(q&&q->info!=x)
|
|
q=q->rlink;/*循环结束后q指向的是值为x的结点*/
|
|
if(!q)
|
|
{
|
|
printf("\n没有找到值为%d的结点!不做删除操作!",x);
|
|
}
|
|
if(q==head&&head->rlink)/*被删除的结点是第一个结点并且表中不只一个结点*/
|
|
{
|
|
head=head->rlink;
|
|
head->llink=NULL;
|
|
free(q);return head;
|
|
}
|
|
if(q==head&&!head->rlink)/*被删除的结点是第一个结点并且表中只有这一个结点*/
|
|
{
|
|
free(q);
|
|
return NULL;/*双链表置空*/
|
|
}
|
|
else
|
|
{
|
|
if(!q->rlink)/*被删除的结点是双链表中的最后一个结点*/
|
|
{
|
|
q->llink->rlink=NULL;
|
|
free(q);
|
|
return head;
|
|
}
|
|
else/*q是在一个有2个以上结点的双链表中的一个非开始、也非终端结点*/
|
|
{
|
|
q->llink->rlink=q->rlink;
|
|
q->rlink->llink=q->llink;
|
|
free(q);
|
|
return head;
|
|
}
|
|
}
|
| } |
3.6链式栈
栈的链式存储称为链式栈。链式栈就是一个特殊的单链表,对于这特殊的单链表,它的插入和删除规定在单链表的同一端进行。链式栈的栈顶指针一般用top表示。
链式栈类型的描述如下:
|
ADT link_stack{
|
|
数据集合K:K={k1, k2,…, kn},n≥0,K中的元素是datatype类型
|
|
数据关系R:R={r}
|
|
r={ <ki, ki+1>| i=1,2,…,n-1}
|
| 操作集合: |
|
(1) node *init_link_stack() 建立一个空链式栈
|
|
(2) int empty_link_stack(node *top) 判断链式栈是否为空
|
|
(3) datatype get_top(node *top) 取得链式栈的栈顶结点值
|
|
(4) void print_link_stack(node *top) 输出链式栈中各个结点的值
|
|
(5) node *push_link_stack(node *top,datatype x) 向链式栈中插入一个值为x的结点
|
|
(6) node *pop_link_stack(node *top) 删除链式栈的栈顶结点
|
|
}ADT link_stack;
|
|
// 取得链式栈的栈顶结点值
|
| |
|
datatype get_top(node *top)
|
|
{
|
|
if(!top)
|
|
{
|
|
printf("\n链式栈是空的!");
|
|
exit(1);
|
|
}
|
|
return(top->info);
|
| } |
|
// 判断链式栈是否为空
|
| |
|
int empty_link_stack(node *top)
|
| { |
|
return (top? 0:1);
|
| } |
|
// 输出链式栈中各个结点的值
|
| |
|
void print_link_stack(node *top)
|
| { |
|
node *p;
|
| |
|
p=top;
|
|
if(!p) printf("\n链式栈是空的!");
|
|
while(p)
|
|
{
|
|
printf("%5d",p->info);
|
|
p=p->next;
|
|
}
|
| } |
|
// 向链式栈中插入一个值为x的结点
|
| |
|
node *push_link_stack(node *top,datatype x)
|
| { |
|
node *p;
|
| |
|
p=(node*)malloc(sizeof(node)); /*分配空间*/
|
|
p->info=x; /*设置新结点的值*/
|
|
p->next=top; /*插入(1)*/
|
|
top=p; /*插入(2)*/
|
|
return top;
|
| } |
|
// 删除链式栈的栈顶结点
|
| |
|
node *pop_link_stack(node *top)
|
| { |
|
node *q;
|
| |
|
if(!top)
|
|
{
|
|
printf("\n链式栈是空的!");
|
|
return NULL;
|
|
}
|
|
q=top;/*指向被删除的结点(1)*/
|
|
top=top->next;/*删除栈顶结点(2)*/
|
|
free(q);
|
|
return top;
|
| } |
3.7链式队列
队列的链式存储称为链式队列。链式队列就是一个特殊的单链表,对于这种特殊的单链表,它的插入和删除规定在单链表的不同端进行。链式队列的队首和队尾指针分别用front和rear表示。
链式队列类型的描述如下:
|
ADT link_queue{
|
|
数据集合K:K={k1, k2,…, kn},n≥0,K中的元素是datatype类型
|
|
数据关系R:R={r}
|
|
r={ <ki, ki+1>| i=1,2,…,n-1}
|
| 操作集合: |
|
(1) queue *init_link_queue() 建立一个空的链式队列
|
|
(2) int empty_link_queue(queue qu) 判断链式队列是否为空
|
|
(3) void print_link_queue(queue *qu) 输出链式队列中各个结点的值
|
|
(4) datatype get_first(queue qu) 取得链式队列的队首结点值
|
|
(5) queue *insert_link_queue(queue *qu,datatype x) 向链式队列中插入一个值为x的结点
|
|
(6) queue *delete_link_queue(queue *qu) 删除链式队列中队首结点
|
|
}ADT link_queue;
|
链式队列的结点定义必须有队首和队尾指针,因此增加定义一个结构类型,其中的两个域分别为队首和队尾指针。其定义如下:
|
typedef struct{
|
|
node *front,*rear; /*定义队首与队尾指针*/
|
|
}queue;
|
|
// 建立一个空的链式队列
|
| |
|
queue *init_link_queue()
|
| { |
|
queue *qu;
|
| |
|
qu=(queue*)malloc(sizeof(queue)); /*分配空间*/
|
|
qu->front=NULL; /*队首指针设置为空*/
|
|
qu->rear=NULL; /*队尾指针设置为空*/
|
|
return qu;
|
| } |
|
// 取得链式队列的队首结点值
|
| |
|
datatype get_first(queue qu)
|
| { |
|
if(!qu.front)
|
|
{
|
|
printf("\n链式队列是空的!");
|
|
exit(1);
|
|
}
|
|
return(qu.front->info);
|
| } |
|
// 向链式队列中插入一个值为x的结点
|
| |
|
queue *insert_link_queue(queue *qu,datatype x)
|
| { |
|
node *p;
|
| |
|
p=(node*)malloc(sizeof(node)); /*分配空间*/
|
|
p->info=x; /*设置新结点的值*/
|
|
p->next=NULL;
|
|
if(qu->front==NULL)
|
|
qu->front=qu->rear=p;
|
|
else
|
|
{
|
|
qu->rear->next=p; /*队尾插入*/
|
|
qu->rear=p;
|
|
}
|
|
return qu;
|
| } |
|
// 删除队首结点
|
| |
|
queue *delete_link_queue(queue *qu)
|
| { |
|
node *q;
|
|
if(!qu->front)
|
|
{
|
|
printf("队列为空,无法删除!");
|
|
return qu;
|
|
}
|
|
q=qu->front; /*q指向队首结点(1)*/
|
|
qu->front=q->next; /*队首指针指向下一个结点(2)*/
|
|
free(q); /*释放原队首结点空间*/
|
|
if(qu->front==NULL)
|
|
qu->rear=NULL; /*队列中的唯一结点被删除后,队列变空(3)*/
|
|
return qu;
|
| } |