第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; |
} |