第1章 概论
数据结构讨论的是数据的逻辑结构、存储方式以及相关操作的实现等问题。本章讲述数据结构的基本概念及相关术语,介绍数据结构、数据类型和抽象数据类型之间的联系,介绍了算法的特点及算法的时间与空间复杂性。
1.1数据结构
1.1.1数据结构
随着计算机软、硬件的发展,计算机的应用范围在不断扩大,计算机所处理的数据的数量也在不断扩大,计算机所处理的数据已不再是单纯的数值数据,而更多的是非数值数据。
需要处理的数据并不是杂乱无章的,它们一定有内在的联系,只有弄清楚它们之间的本质的联系,才能使用计算机对大量的数据进行有效的处理。
使用计算机处理用户信息表中的数据时,必须弄清楚下面3个问题:
1) 数据的逻辑结构(各结点的关系)
2) 数据的存储结构(存入计算机中的结构)
3) 数据的运算集合(相关运算,如删除、添加等)
数据结构就是指按一定的逻辑结构组成的一批数据,使用某种存储结构将这批数据存储于计算机中,并在这些数据上定义了一个运算集合。
1.1.2数据的逻辑结构
例如,有5个人,分别记为a,b,c,d,e,其中a是b的父亲,b是c的父亲,c是d的父亲,d是e的父亲。
如果只讨论他们之间所存在的父子关系,则可以用二元组形式化地予以表达:B=(K,R)
其中:K={a,b,c,d,e} R={r} r={<a, b>,<b,c>, <c, d>,<d,e>}
1.1.3数据的存储结构
对于一个数据结构B=(K,R),必须建立从结点集合到计算机某个存储区域M的一个映象,这个映象要直接或间接地表达结点之间的关系R。数据在计算机中的存储方式称为数据的存储结构。
数据的存储结构主要有4种:
1) 顺序存储
顺序存储通常用于存储具有线性结构的数据。将逻辑上相邻的结点存储在连续存储区域M的相邻的存储单元中,使得逻辑相邻的结点一定是物理位置相邻。
2) 链式存储
链式存储方式是给每个结点附加一个指针段,一个结点的指针所指的是该结点的后继的存储地址,因为一个结点可能有多个后继,所以指针段可以是一个指针,也可以是一个多个指针。
3) 索引存储
在线性结构中,设开始结点的索引号为1,其它结点的索引号等于其前继结点的索引号加1,则每一个结点都有唯一的索引号,索引号就是根据结点的索引号确定该结点的存储地址。
4) 散列存储
散列存储的思想是构造一个从集合K到存储区域M的一个函数h,该函数的定义域为K,值域为M,K中的每个结点ki在计算机中的存储地址由h(ki)确定。
1.1.4数据的运算集合
对于一批数据,数据的运算是定义在数据的逻辑结构之上的,而运算的具体实现就依赖于数据的存储结构。
数据的运算集合要视情况而定,一般而言,数据的运算包括插入、删除、检索、输出、排序等。
1.2数据类型和抽象数据类型
在程序设计中,数据和运算是两个不可缺少的因素。所有的程序设计活动都是围绕着数据和其上的相关运算而进行的。从机器指令、汇编语言中的数据没有类型的概念,到现在的面向对象程序设计语言中抽象数据类型概念的出现,程序设计中的数据经历了一次次抽象,数据的抽象经历了三个发展阶段:
Ø从无类型的二进制数到基本数据类型的产生
Ø从基本数据类型到用户自定义类型的产生
Ø从用户自定义类型到抽象数据类型的出现
1.2.1数据类型
数据类型(或简称类型)反映了数据的取值范围以及对这类数据可以施加的运算。
1.2.2数据结构
数据结构包括三个方面的内容:一组数据中各数据之间的逻辑关系;这组数据在计算机中的存储方式;对这组数据所能施加的运算的集合。数据结构是数据存在的形式。所有的数据都是按照数据结构进行分类的。简单数据类型对应于简单的数据结构;构造数据类型对应于复杂的数据结构。
1.2.3抽象数据类型
抽象数据类型是与表示无关的数据类型,是一个数据模型及定义在该模型上的一组运算。对一个抽象数据类型进行定义时,必须给出它的名字及各运算的运算符名,即函数名,并且规定这些函数的参数性质。一旦定义了一个抽象数据类型及具体实现,程序设计中就可以像使用基本数据类型那样,十分方便地使用抽象数据类型。
1.2.4抽象数据类型的描述和实现
抽象数据类型的描述包括给出抽象数据类型的名称、数据的集合、数据之间的关系和操作的集合等方面的描述。抽象数据类型的设计者根据这些描述给出操作的具体实现,抽象数据类型的使用者依据这些描述使用抽象数据类型。
抽象数据类型描述的一般形式如下:
ADT 抽象数据类型名称 { 数据对象: …… 数据关系: …… 操作集合: 操作名1: …… …… 操作名n: }ADT抽象数据类型名称
1.3 算法和算法分析
1.3.1算法
为了求解某问题,必须给出一系列的运算规则,这一系列的运算规则是有限的,表达了求解问题方法和步骤,这就是一个算法。
算法具有五个基本特征:
①有穷性,算法的执行必须在有限步内结束。
②确定性,算法的每一个步骤必须是确定的无二义性的。
③输入, 算法可以有0个或多个输入。
④输出, 算法一定有输出结果。
⑤可行性,算法中的运算都必须是可以实现的。
算法具有有穷性,程序不需要具备有穷性。一般的程序都会在有限时间内终止,但有的程序却可以不在有限时间内终止,如一个操作系统在正常情况下是永远都不会终止的。
1.3.2算法的时间和空间复杂性
一个算法的优劣主要从算法的执行时间和所需要占用的存储空间两个方面衡量,算法执行时间的度量不是采用算法执行的绝对时间来计算的,因为一个算法在不同的机器上执行所花的时间不一样,在不同时刻也会由于计算机资源占用情况的不同,使得算法在同一台计算机上执行的时间也不一样,所以对于算法的时间复杂性,采用算法执行过程中其基本操作的执行次数,称为计算量来度量。
算法中基本操作的执行次数一般是与问题规模有关的,对于结点个数为n的数据处理问题,用T(n)表示算法基本操作的执行次数。
在评价算法的时间复杂性时,不考虑两算法执行次数之间的细小区别,而只关心算法的本质差别。为此,引入一个所谓的O() 记号,则T1(n)=2n=O(n),T2(n)=n+1=O(n)。
一个函数f(n)是O(g(n))的,则一定存在正常数c和m,使对所有的n>m,都满足f(n)<c*g(n)。
算法的时间复杂性不仅和问题的规模大小有关,还与问题数据的初始状态有关。
这样就有了算法在最好、最坏以及在平均状态下的时间复杂性的概念。
①算法在最好情况下的时间复杂性是指算法计算量的最小值。
②算法在最坏情况下的时间复杂性是指算法计算量的最大值。
③算法的平均情况下的时间复杂性是指算法在所有可能的情况下的计算量经过加权计算出的平均值。
第2章 线性表及其顺序存储
顺序表:
顺序表类型的描述如下:
ADT sequence_list{ 数据集合K:K={k1, k2,…, kn},n≥0,K中的元素是datatype类型 数据关系R:R={r},r={ <ki, ki+1>| i=1,2,…,n-1} 操作集合: (1) void init_sequence_list(sequence_list *slt) 顺序表的初始化------置空表 (2) void insert_sequence_list(sequence_list *slt,datatype x) 后部插入值为x结点 (3) void print_sequence_list(sequence_list slt) 打印顺序表的各结点值 (4) int is_empty_sequence_list(sequence_list slt) 判断顺序表是否为空 (5) int find_num_sequence_list(sequence_list slt,datatype x) 查找值为x结点位置 (6) int get_data_pos(sequence_list slt,int i) 取得顺序表中第i个结点的值 (7) void insert_pos_sequence_list(sequence_list *slt,int position,datatype x) (8) void delete_pos_sequence_list(sequence_list *slt,int position) } ADT sequence_list;
// sequlist.h 头文件 #define MAXSIZE 100 typedef int datatype; typedef struct{ datatype a[MAXSIZE]; int size; }sequence_list;
// seqlinit.c 初始化--置空表 void init_sequence_list(sequence_list *slt) { slt->size = 0; }
// seqlinse.c 后部插入值为x结点 void insert_sequence_list(sequence_list *slt, datatype x) { if(slt->size==MAXSIZE) { printf("sequence_list is full!"); exit(1); } slt->size=slt->size+1; slt->a[slt->size]=x; }
// seqlprin.c 打印各结点值 void print_sequence_list(sequence_list slt) { int i; if(!slt.size) printf("the sequence_list is empty!\n"); else for(i=0; i<slt.size; i++) printf("%5d", slt.a[i]); }
// seqlempt.c 判断顺序表是否为空 int is_empty_sequence_list(sequence_list slt) { return(slt.size==0?0:1); }
// seqlfind.c 查找表中值为x的结点位置 int find_num_sequence_list(sequence_list slt, datatype x) { int i = 0; while(slt.a[i]!=x&&i<slt.size) i++; return(i<slt.size?i:-1); }
// seqlget.c 取得顺序表中第i个结点的值 int get_data_pos(sequence_list slt, int i) { if(i<0||i>=slt.size) { printf("wrong node!\n"); exit(1); } else return slt.a[i]; }
// 在顺序表的position位置插入值为x的结点 void insert_pos_sequence_list(sequence_list *slt, int position, datatype x) { int i; if(slt->size==MAXSIZE) { printf("the sequence_list is full!can't insert!\n"); exit(1); } if(position<0||position>slt->size) { printf("wrong position!\n"); exit(1); } for(i=slt->size; i>position; i--) slt->a[i]=slt->a[i-1]; slt->a[position] = x; slt->size++; }
// 删除顺序表中第position位置的结点 void delet_pos_sequence_list(sequence_list *slt, int position) { int i; if(slt->size==0) { printf("the sequence_list is empty!\n"); exit(1); } if(position<0||position>=slt->size) { printf("wrong position!\n"); exit(1); } for(i=position; i<slt->size-1; i--) slt->a[i] = slt->a[i+1]; slt->size--; }
顺序栈及其实现:
栈类型的描述如下:
ADT sequence_stack { 数据集合K:K={k1, k2,…, kn},n≥0,K中的元素是datatype类型 数据关系R:R={r} r={ <ki, ki+1>| i=1,2,…,n-1} 操作集合: (1) void init_sequence_stack(sequence_stack *st) (顺序存储)初始化 (2) int is_empty_stack(sequence_stack st) 判断栈(顺序存储)是否为空 (3) void print_sequence_stack(sequence_stack st) 打印栈(顺序存储)的结点值 (4) datatype get_top(sequence_stack st) 取得栈顶(顺序存储)结点值 (5) void push(sequence_stack *st,datatype x) 栈(顺序存储)的插入操作 (6) void pop(sequence_stack *st) 栈(顺序存储)的删除操作 } ADT sequence_stack
设定一个足够大的一维数组存储栈,数组中下标为0的元素就是栈底,对于栈顶,可以设一个指针top指示它。
设定top所指的位置是下一个将要插入的结点的存储位置,这样,当top=0时就表示是一个空的栈。
// seqstack.h #define MAXSIZE 100 typedef int datatype; typedef struct{ datatype a[MAXSIZE]; int top; }sequence_stack;
// seqsinit.c void init_sequence_stack(sequence_stack *st) { st->top = 0; }
// seqsempt.c int is_empty_stack(sequence_stack st) { return(st.top?0:1); }
// seqsfirs.c 取得栈顶结点值 datatype get_top(sequence_stack st) { if(is_empty_stack(st)) { printf("the stack is empty!\n"); exit(); } else return st.a[st.top-1]; }
// seqspush.c 栈的插入操作 void push(sequence_stack *st, datatype x) { if(st->top==MAXSIZE) { printf("the sequence stack if full!\n"); exit(1); } st->a[st->top] = x; st->top++; }
// seqspop.c 栈的删除操作 void pop(sequence_stack *st) { if(st->top==0) { printf("the seqence stack is empty!\n"); exit(1); } st->top--; }
栈的应用——括号匹配
设一个表达式中可以包含三种括号:小括号、中括号和大括号,各种括号之间允许任意嵌套,如小括号内可以嵌套中括号、大括号,但是不能交叉。
int match_kuohao(char c[]) { int i=0; sequence_stack s; init_sequence_stack(&s); while(c[i]!='#') { switch(c[i]) { case '{': case '[': case '(':push(&s,c[i]); break; case '}':if(!is_empty_sequence_stack(s)&&get_top(s)=='{') { pop(&s); break; } else return 0; case ']':if(!is_empty_sequence_stack(s)&&get_top(s)=='[') { pop(&s); break; } else return 0; case ')':if(!is_empty_sequence_stack(s)&&get_top(s)=='(') { pop(&s); break; } else return 0; } i++; } return (is_empty_sequence_stack(s));/*栈空则匹配,否则不匹配*/ }
队列
队列的插入和删除操作分别在表的两端进行。插入的那一端称为队尾,删除的那一端称为队首。队列的插入操作和删除操作也分别简称进队和出队。
队列类型的描述如下:
ADT sequence_queue { 数据集合K:K={k1, k2,…, kn},n≥0,K中的元素是datatype类型 数据关系R:R={r} r={ <ki, ki+1>| i=1,2,…,n-1} 操作集合: (1) void init_sequence_queue(sequence_queue *sq) 队列(顺序存储)初始化 (2) int is_empty_sequence_queue(sequence_queue sq) 判断队列(顺序存储)是否为空 (3) void print_sequence_queue(sequence_queue sq) 打印队列(顺序存储)的结点值 (4) datatype get_first(sequence_queue sq) 取得队列(顺序存储)的队首结点值 (5) void insert_sequence_queue (sequence_queue *sq,datatype x) 队列(顺序存储)插入操作 (6) void delete_sequence_queue(sequence_queue *sq) 队列(顺序存储)的删除操作 }ADT sequence_queue;
// seqqueue.h #define MAXSIZE 100 typedef int datatype; typedef struct{ datatype a[MAXSIZE]; int front; int rear; }sequence_queue;
// seqqinit.c void init_sequence_queue(sequence_queue *sq) { sq->front = sq->rear = 0; }
// seqqempt.c int is_empty_sequence_queue(sequence_queue sq) { return(sq.front==sq.rear?1:0); }
// seqqprin.c void print_sequence_quence(sequence_queue sq) { int i; if(is_empty_sequence_queue(sq)) { printf("the sequence_queue is empty!"); } else { for(i=sq.front; i<sq.rear; i++) printf("%5d", sq.a[i]); } }
// seqqinse.c void insert_sequence_queue(sequence_queue *sq, datatype x) { int i; if(sq->rear==MAXSIZE) { printf("the sequence_queue is full!"); exit(1); } sq->a[sq->rear] = x; sq->rear = sq->rear + 1; }
// seqqdele.c void delete_sequence_queue(sequence_queue *sq) { if(sq->front==rq->rear) { printf("the sequence_queue is empty!can't delete!\n"); exit(1); } sq->front++; }
循环队列:
循环队列满的条件是:(rear+1)%MAXSIZE=front
循环队列空的条件是:rear=front
// secqinst.c void insert_sequence_cqueue(sequence_queue *sq,datatype x) { int i; if((sq->rear+1)%MAXSIZE==sq->front) { printf("the sequence_queue is full! can’t insert!"); exit(1); } sq->a[sq->rear]=x; sq->rear=(sq->rear+1)%MAXSIZE; }
// secqdele.c void delete_sequence_cqueue(sequence_queue *sq) { if(sq->front==sq->rear) { printf("the sequence_queue is empty!can't delete!\n"); exit(1); } sq->front=(sq->front+1)%MAXSIZE; }
第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; }