[数据结构]王道数据结构练习题
线性表
数据结构类型定义:
线性表(顺序存储类型描述):
#define MaxSize 50 //定义线性表的最大长度
typedef struct {
ElemType data[MaxSize]; //顺序表的元素
int length; //顺序表的当前长度
} SqList; //顺序表的类型定义
线性表(动态存储类型描述)
#define InitSize 100 //表长度的初始定义
typedef struct {
ElemType *data; //指示动态分配数组的指针
int MaxSize,length; //数组的最大容量和当前个数
} SeqList; //动态分配数组顺序表的类型定义
L.data = (ElemType*)malloc(sizeof(ElemType)*InitSize);//初始内存分配
程序设计题
程序设计题
题1
1.从顺序表中删除具有最小值的元素(假设唯一)并由函数返回被删元素的值.空出的位置由最后一个
元素填补,若顺序表为空则显示出错信息并退出运行.
//思想:遍历整个顺序表,查找最小值元素并记录其位置,遍历结束后用最后一个元素填补空的原最小值元素位置.
bool Del_MinElem(sqList &L,ElemType &value){
//删除顺序表L中最小值元素结点,并通过引用型参数value返回其值
//bool类型:若删除成功,则返回ture;否则,返回false
if (L.length == 0) return false; //表空,中止操作返回
value = L.data[0];
int pos = 0; //假定0号元素的值最小
//循环,寻找具有最小值的元素
for (int i = 1; i < L.length; i++){
//利用value记录当前具有最小值的元素
if (L.data[i] < value){
value = L.data[i];
pos = i;
}
}
//空出来的位置由最后一个元素填补
L.data[pos] = L.data[L.length - 1];
L.length--;
//此时,value即为最小值
return true;
}
题2
2.设计一个高效的算法,将顺序表的所有元素逆置,要求算法的空间复杂度为O(1).
//思想:扫描顺序表L的前半部分元素,对于元素L.data[i](0<i<L.length/2),
//将其余后半部分对应元素L.data[L.length-i-1]进行交换
void Reverse(SqList &L){
//实现元素逆置
ElemType temp;//辅助变量
for (int i = 0; i < L.length/2; i++){
temp = L.data[i];
L.data[i] = L.data[L.length-i-1];
L.data[L.length-i-1] = temp;
}
}
题3
3.长度为n的顺序表L,编写一个时间复杂度为O(n),空间复杂度为O(1)的算法,该算法删除线性表中
所有值为x的数据元素.
//思想:用k记录顺序表L中不等于x的元素个数(即需要保存的元素个数),边扫描L边统计k
//并将不等于x的元素向前放置k位置上,最后修改L的长度.
void del_x(SqList &L, ElemType x){
//删除顺序表L中所有值为x的数据元素
int k = 0;
for (int i = 0; i < L.length; i++){
if (L.data[i] != x){
L.data[k] = L.data[i];
k++;
}
}
}
题4
4.从有序顺序表中删除其值在给定值s与t之间(要求s<t)的所有元素.如果s或t不合理或者
顺序表为空则显示出错信息并退出运行.
//思想:先寻找值大于等于s的第一个元素(第一个删除的元素),然后寻找值>t的第一个元素
//(最后一个删除的元素的下一个元素),要将这段元素,则只需直接将后面的元素前移即可.
bool Del_s_t(SqList &L,ElemType s, ElemType t){
//删除有序顺序表L中值在给定值s与t之间的所有元素
int i,j;
if (s >= t || L.length == 0) return false;
//寻找值>=s的第一个元素
for (i = 0; i < L.length && L.data[i] < s; i++)
//所有元素值均小于s,则返回
if (i >= L.length) return false;
//寻找值>t的第一个元素
for (j = i; j < L.length && L.data[j] <= t; j++);
for (;j < L.length; i++,j++)
L.data[i] = L.data[j];//前移,填补被删元素位置
L.length = i+1;
return true;
}
题5
5.从顺序表中删除其值在s与t之间(包含s和t,要求s<t)的所有元素,如果s或t不合理或者
顺序表为空则显示出错信息并退出运行.
//思想:从前向后扫描顺序表L,用k记录下元素值在s到t之间元素的个数(初始时k=0)
//对于当前扫描的元素,若其值不在s到t之间,则前移k个位置;
//否则执行k++,由于这样每个不在s到t之间的元素仅移动一次.所以算法效率高.
bool Del_s_t(SqList &L, ElemType t){
//删除顺序表L中值在给定值s与t之间(s<t)的所有元素
int i,k = 0;
if (L.length == 0 || s >= t) return false;//线性表为空或s>t不合法,返回
for (int i = 0; i < L.length; i++){
if (L.data[i] >= s && L.data[i] <= t){
k++;
} else {
L.data[i-k] = L.data[i];//当前元素前移动k个位置
}
}//for
L.length--k;//长度减小
return true;
}
题6
6.从有序顺序表中删除所有其值重复的元素,使表中所有元素的值均不同.
//思想:注意到是有序顺序表,值相同的元素一定在连续的位置上,用类似于直接插入排序的思想,
//初始化时将第一个元素看做非重复的有序表.之后依次判断后面的元素是否与前面非重复有序
//表的最后一个元素相同,若如果相同则继续向后判断,如果不同则插入到前面的非重复有序表的
//最后,直至判断到表尾为止.
bool Delete_Same(SeqList &L){
if (L.length == 0) return false;
int i,j;//i存储第一个不相同的元素,j为工作指针
for (i = 0,j = 1; j < L.length; j++){
//查号下一个与上个元素值不同的元素
if (L.data[i] != L.data[j]){
//找到后,则将元素前移
L.data[++i] = L.data[j];
}
}
L.length = i + 1;
return true
}
题7
7.将两个有序顺序表合并成一个新的有序顺序表,并由函数返回结果顺序表.
//思想:首先,按照顺序不断取下两个顺序表表头较小的结点存入新的顺序表中.
//然后,看哪个表还有剩余,把剩余的部分加到新的顺序表后面
bool Merge(SeqList A,SeqList B,SeqList &C){
//合并有序顺序表A和B成为一个新的有序顺序表C
if (A.length + B.length > C.maxSize) //大于顺序表的最大长度
return false;
int i = 0,j = 0, k = 0;
//循环两两比较,小者存入结果表
while (i < A.length && j < B.length){
if (A.data[i] <= B.data[j]){
C.data[k++] = A.data[i++];
} else {
C.data[k++] = B.data[j++];
}
}
//还剩一个没有比较完的顺序表
while (i < A.length){
C.data[k++] = A.data[i++];
}
while (j < B.length){
C.data[k++] = B.data[j++];
}
C.length = k + 1;
return true;
}
题8
8.已知在一维数组A[m+n]中依次存放着两个线性表(a1,a2,a3,...,am)和(b1,b2,..,bn)
试编写一个函数,将数组中两个顺序表的位置互换,即将(b1,b2,..,bn)放在(a1,a2,..,am)
的前面.
//思想:先将数组中的全部元素原地逆置,再对前n个元素和后m个元素分别识别逆置算法
//就可以得到,从而实现顺序表的位置互换
typedef int DataType;
void Reverse(DataType A[], int left, int right, int arraySize){
//逆置翻转
if (left >= right || right >= arraySize) return;
int mid = (left+right) /2 ;
for (int i = 0; i < mid - left; i++){
DataType temp = A[left+i];
A[left+i] = A[right-i]
A[right-i] = temp;
}
}
void Exchange(DataType A[], int m, int n, int arraySize){
//顺序表互换
Reverse(A,0,m+n-1,arraySize);
Reverse(A,0,n-1,arraySize);
Reverse(A,n,m+n-1,arraySize);
}
题9
9.线性表(a1,a2,a3,..,an)中元素递增有序且按顺序存储于计算机内.要求设计一算法完成
用最少时间在表中查找数值为x的元素,若找到将其与后继元素位置相互交换,若找不到将其插入
表中并使表中元素仍递增有序.
//思想:顺序存储的线性表递增有序,可以顺序查找,也可折半查找
void SearchExchangeInsert(ElemType A[], ElemType x){
int low = 0,high = n-1,mid;//low和high指向顺序表下界和上界的下标
while (low < high){
mid = (low+high) / 2;//找中间位置
if (A[mid] == x) break;//找到x,退出循环
else if (A[mid]<x) low = mid + 1;//查找中点的左部分
else high = mid -1;//查找中点的右部分
}
//若最后一个元素与x相等,则不存在与其后继交换的操作
if (A[mid] == x && mid != n-1){
t = A[mid];
A[mid] = A[mid+1];
A[mid+1] = t;
}
//查找失败,插入数据元素x
if (low > high){
//后移元素
for (i = n - 1; i > high; i--){
A[i+1] = A[i];
}
//插入x
A[i+1] = x;
}//结束charu
//程序终止
}
题10
10.[2010年计算机联考真题]
设将n(n>1)个整数存放到一维数组R中.试设计一个在时间和空间两方面都尽可能高效的算法.
将R中保存的序列循环左移p(0<p<n)个位置,即将R中的数据由(x0,x1,..,xn-1)变换为
(xp,xp+1,..,xn-1,x0,x1,..,xp-1).
要求:
(1)给出算法的基本设计思想
(2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释.
(3)说明你设计算法的时间复杂度和空间复杂度
//思想:
//Reverse(0,p-1)得到cbadefgh
//Reverse(p,n-1)得到cbahgfed
//Reverse(0,n-1)得到defghabc
void Reverse(int R[], int from, int to){
int i,temp;
for (i = 0; i < (to-from+1)/2; i++){
temp = R[from+1];
R[from+i] = R[to-i];
R[to-i] = temp;
}
}//Reverse
void Converse(int R[], int n, int p){
Reverse(0,p-1);
Reverse(p,n-1);
Reverse(0,n-1);
}
//时间复杂度分别为O(p/2),O((n-p)/2),O(n/2)
题11
11.[2011年计算机联考真题]
一个长度为L(L>1)的升序序列S,处在[L/2]个位置的数称为S的中位数.
例如,若序列S1 = (11,13,15,17,19),则S1的中位数为15.
两个序列的中位数是含它们所有元素的升序序列的中位数.
例如,若S2=(2,4,6,8,20),则S1和S2的中位数为11.
现在有两个等长升序序列A和B,试设计一个在时间和空间两个方面都尽可能高效的算法,找出
两个序列A和B的中位数.
要求:
(1)给出算法的基本设计思想
(2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释.
(3)说明你设计算法的时间复杂度和空间复杂度
//思想:
//分别求两个升序序列A,B的中位数,设为a和b,求序列A,B的中位数如下:
//1.若a=b,则a或b即为所求中位数,算法结束
//2.若a<b,则舍弃序列A中较小的一半,同时舍弃序列B中较大的一半,要求两个设计的长度相等
//3.若a>b,则舍弃序列A中较大的一半,同时舍弃序列B中较大的一半,要求两个舍弃的长度相等
int M_Search(int A[], int B[], int n){
int s1 = 0,d1 = n-1,m1;
int s2 = 0,d2 = n-1,m2;
//分别表示序列A和序列B的首位数,末尾数和中位数
while (s1 != d1 || s2 != d2){
m1 = (s1+d1)/2;
m2 = (s2+d2)/2;
if (A[m1] == B[m2]) //满足条件1
return A[m1];
if (A[m1] < B[m2]){//满足条件2
if ((s1+d1)%2 == 0){//若元素个数为奇数
s1 = m1;//舍弃A中间点以前的部分且保留中间点
d2 = m2;//舍弃B中间点以后的部分且保留中间点
} else {//若元素为偶数
s1 = m1 + 1;//舍弃A中间点以前的部分且保留中间点
d2 = m2;//舍弃B中间点以后的部分且保留中间点
}
}
else {//满足条件3
if ((s2+d2)%2 == 0){//若元素个数为奇数
d1 = m1;//舍弃A中间点以前的部分且保留中间点
s2 = m2;//舍弃B中间点以后的部分且保留中间点
}
else {
d1 = m1;//舍弃A中间点以前的部分且保留中间点
s2 = m2 + 1;//舍弃B中间点以后的部分且保留中间点
}
}
}
return A[s1] < B[s2] ? A[s1]:B[s2];
}
题12
12.[2013年计算机联考真题]
已知一个整数序列A=(a0,a1,..,an-1),其中0<ai<n(0<i<n).若存在ap1 = ap2 = .. = apm = x
且m > n/2(0<pk<n,1<k<m)则称x为A的主元素.例如A= (0,5,5,3,5,7,5,5),则5为主元素;
又如A=(0,5,5,3,5,1,5,7),则A中没有主元素.假设A中的n个元素保存在一个一维数组中,请设计
一个尽可能高效的算法,找出A的主元素.若存在主元素,则输出该元素;否则输出-1.
要求:
(1)给出算法的基本设计思想
(2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释.
(3)说明你设计算法的时间复杂度和空间复杂度
//思想:
//笨算法的策略是从前往后扫描数组元素,标记出一个可能成为主元素的元素Num.
//然后重新计数,确认Num是否为元素
//算法分为以下两步:
//1.选取候选的主元素:依次扫描所给的数组中的每个整数,将第一个遇到的整数Num保存到c中
//记录Num的出现的次数为1;若遇到下一个整数仍等于Num,则计数加1,否则计数减1;
//当计数减到0时,将遇到的下一个整数保存在c中,计数重新记为1,开始新的一轮计数
//即从当前位置开始重复上述过程,直到扫描完全部数组元素.
//2.判断c中元素是否是真的主元素:再次扫描该数组,统计c中元素出现的次数,若大于n/2,
//则为主元素;否则,序列中不存在主元素
int Majority(int A[], int n){
int i,c,count = 1;//c用来保存候选的主元素,count用来计数
c = A[0];//设置A[0]为候选主元素
for (i = 1; i < n; i++)
if (A[i] == c){//对A中的候选主元素进行计数
count++;
}
else {
if (count > 0)//处理不是候选主元素的情况
count--;
else {//重新更换候选主元素,重新计数
c = A[i];
count = 1;
}
}
if (count > 0)
for (i = count = 0; i < n; i++)//统计候选主元素的实际出现次数
if (A[i] == c)
count++;
if (count > n/2) return c;//确认候选主元素
else return -1;//不存在主元素
}
链表——线性表的链式表示
结点类型描述
单链表的结点类型描述:
typedef struct LNode{ //定义单链表结点类型
ElemType data; //数据域
struct LNode *next; //指针域
}LNode,*LinkList;
双链表的结点类型描述:
typedef struct DNode{ //定义双链表结点类型
ElemType data; //数据域
struct DNode *prior,*next; //前驱和后继指针
}DNode,*DLinkList;
静态链表结点类型的描述:
#define MaxSize 50 //静态链表的最大长度
typedef struct { //静态链表结构类型的定义
ElemTypen data; //存储数据元素
int next; //下一个元素的数组下标
} SLinkList[MaxSize];
程序设计题
题1
1.设计一个递归算法,删除不带头结点的单链表L中所有值为x的结点
终止条件:f(L,x) = 不做任何事情 //若L为空表
递归主体:f(L,x) = 删除*L结点;
f(L->next,x) //若L->data = x
f(L,x) = f(L->next,x)//其他情况
void Del_x(LinkList &L,ElemType x){
//递归实现在单链表L中删除值为x的jied
LNode *p; //p指向待删除结点
if (L == NULL) return; //递归出口
if (L -> data == x){ //若L所指结点的值为x
p = L; //删除*L,并让L指向下一结点
L = L -> next;
free(p);
Del_x(L,x); //递归调用
} else { //若L所指的结点的值不为x
Del_x(L->next,x); //递归调用
}
}
算法需要借助一个递归工作栈,深度为O(n),时间复杂度为O(n).
题2
2.在带头结点的单链表L中,删除所有值为x的结点,并释放其空间.
假设值为x的结点不唯一,试编写算法以实现上述操作.
//思想:用p从头至尾扫描单链表,pre指向*p结点的前驱.
//若p所指结点的值为x,则删除,并让p移向下一个结点
//否则让pre,p指针同步后移一个结点.
void Del_x(LinkList &L,ElemType x){
//L为带头结点的单链表,本算法删除L中所有值为x的结点
LNode *p = L -> next,*pre=L,*q; //置p和pre的初始值
while (p != NULL){
if (p->data == x){
q = p; //q指向该结点
p = p->next;
pre -> next = p; //删除*q结点
free(q); //释放*q结点的空间
}
else { //否则,pre和p同步后移
pre = p;
p = p -> next;
}//else
}//while
}
题3
3.设L为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值
//思想:利用栈和递归的思想来实现,每当访问一个结点时,先递归输出它后面的结点,再输出该结点自身
void R_Print(LinkList L){
//从尾到头输出单链表L中每个结点的值
if (L->next != NULL){
R_Print(L->next);//递归
}//if
print(L->data);//输出函数
}
题4
4.试编写在带头结点的单链表L中删除一个最小值界定啊的高效算法(假设最小值结点是唯一的)
//思想:用p从头至尾扫描单链表,pre指向*p结点的前驱,用minp保存值最小的结点指针(初值为p)
//minpre指向*minp结点的前驱(初值为pre).一边扫描,一边比较.
//若p->data小于minp->data,则将p,pre分别赋值给minp,minpre.
//当p扫描完毕时,minp指向最小值结点.
//minpre指向最小值结点的前驱结点,再将minp所指结点删除即可
LinkList Delete_Min(LinkList &L){
//L为带头结点的单链表,本算法删除其最小值结点
LNode *pre = L,*p = pre->next; //p为工作指针,pre指向其前驱
LNode *minpre = pre,*minp = p; //保存最小值结点及其前驱
while (p != NULL){
if (p -> data < minp -> data){
minp = p; //找到比之前找到的最小值结点更小的结点
minpre = pre;
}
pre = p; //继续扫描下一个结点
p = p -> next;
}
minpre -> next = minp -> next;//删除最小值结点
free(minp);
return L;
}
题5
5.试编写算法将带头结点的单链表就地逆置,所谓就地是指辅助空间复杂度为O(1).
//思想:将头结点摘下,然后从第一结点开始,依次前插入到头结点的后面
//直到最后一个结点为止,则实现了链表的逆置
LinkList Reverse(LinkList L){
//L是带头结点的单链表,将L就地逆置
LNode *p,*r; //p为工作指针,r为p的后继,以防断链
p = L -> next; //从第一个元素结点开始
L -> next = NULL; //先将头结点L的next域置为NULL
while (p != NULL){ //依次将元素结点摘下
r = p -> next; //暂存p的后继
p -> next = L -> next; //将p结点插入到头结点之后
L -> next = p;
p = r;
}
return L;
}
题6
6.试编写算法将带头结点的单链表L,设计一个算法使其元素递增有序.
//思想:采用直接插入排序算法的思想,先构成只含一个数据结果的有序单链表,然后依次扫描单链表
//中剩下的结点*p(直至p==NULL),在有序表中通过比较查找插入*p的前驱结点*pre
//然后将*p插入到*pre之后.
void Sort(LinkList &L){
LNode *p = L -> next,*pre;
LNode *r = p -> next;
p -> next = NULL;
p = r;
while (p != NULL){
r = p -> next;
pre = L;
while (pre->next != NULL && pre->next->data < p->data){
pre = pre -> next; //在有序表中查找插入*p的前驱结点*pre
p -> next = pre -> next; //将*p插入到*pre之后
pre -> next = p;
p = r; //扫描原单链表中剩下的结点
}
}
题7
7.设在带表头结点的单链表中所有元素结点的数据值无序,试编写一个函数,删除表中所有介于给定
的两个值(作为函数参数给出)之间的元素的元素(若存在)
void RangeDelete(LinkList &L, int min, int max){
LNode *pr = L,*p = L->link; //p是检测指针,pr是其前驱
while (p != NULL){
if (p->data > min && p ->data < max){ //寻找到被删结点,删除
pr->link = p ->link;
free(p);
p = pr -> link;
}
else { //否则继续寻找被删除结点
pr = p;
p = p -> link;
}
}
}
题8
8.给定两个单链表,编写算法找出两个链表的公共结点
//思想:先分别遍历两个链表得到它们的长度,并求长度之差.
//在长链表上先遍历长度之差个结点之后,再同步遍历两个链表
//知道找到相同的结点,或者一直到链表结束.
LinkList Search_lst_common(LinkList L1, LinkList L2){
//分别计算两个链表的表长
int len1 = L1.length;
int len2 = L2.length;
LinkList longList,shortList;//分别指向表长较长和表长较短的链表
if (len1 > len2){
longList = L1 -> next;
shortList = L2 -> next;
int dist = len1 - len2 ;
} else {
longList = L2 -> next;
shortList = L1 -> next;
int dist = len2 - len1;
}
while (dist--)
longList = longList -> next;
while (longList != shortList){
if (longList == shortList)
return longList;
else {
longList = longList -> next;
shortList = shortList -> next;
}
}//while
return NULL;
}
题9
9.给定一个带表头结点的单链表,设head为头指针,结点结构为(data,next)
data为整型元素,next为指针,试写出算法:按递增次序输出单链表中各结点的数据元素,
并释放结点所占的存储空间(要求:不允许使用数组作为辅助空间)
//思想:对链表进行遍历,在每趟遍历中查找出整个链表的最小值元素,输出并释放结点所占空间;
//再查找次小值元素,输出并释放空间,如此下去,直到链表为空,最后释放头结点所占存储空间
void Min_Delete(LinkList &head){
//循环到仅剩头结点
while (head -> next != NULL){
pre = head; //pre为元素最小值结点的前驱结点的指针
p = pre -> next; //p为工作指针
while (p -> next != NULL){
if (p->next->data < pre->next->data){
pre = p; //记住当前最小值结点的前驱
p = p -> next;
}
}
print(pre->next->data); //输出元素最小值结点的数据
u = pre -> next; //删除元素值最小的结点,释放结点空间
pre -> next = u -> next;
free(u);
}//while
free(head); //释放头结点
}
题10
10.将一个带头结点的单链表A分解为两个带头结点的单链表A和B,使得A表中含有原表中序号为
奇数的元素,而B表中含有原表中序号为偶数的元素,且保持其相对顺序不变.
//思想:设置一个访问序号变量(初始值为0),每访问一个结点序号自动加1,然后根据序号的奇偶性
//将结点插入到A表或者B表中.重复以上操作直到表尾
LinkList DisCreate(LinkList &A){
int i = 0; //i记录表A中结点的序号
B = (LinkList)malloc(sizeof(LNode)); //创建B表表头
B -> next = NULL; //B表的初始化
LNode *ra = A,*rb = B; //ra和rb分别指向将创建的A表和B表的尾结点
p = A -> next; //p为链表工作指针,指向待分解的结点
A -> next = NULL; //置空新的A表
while (p != NULL){
i++; //序号+1
if (i%2 == 0){ //处理序号为偶数的链表结点
rb -> next = p; //在B表尾插入新结点
rb = p; //rb指向新的尾结点
}
else { //处理原序号为奇数的结点
ra -> next = p; //在A表尾插入新结点
ra = p;
}
p = p -> next; //将p恢复为指向新的待处理结点
}//while结束
ra -> next = NULL;
rb -> next = NULL;
return B;
}
题11
11.设C={a1,b2,a2,b2,...,an,bn}为线性表,采用带头结点的hc单链表存放,设计一个就地
算法,将其拆分为两个线性表,使得
A = {a1,a2,...,an}, B = {bn,bn-1,...,bn,b1}
LinkList DisCreate(LinkList &L){
LinkList B = (LinkList)malloc(sizeof(LNode));
B -> next = NULL; //B表的初始化
LNode *p = A -> next,*q; //p为工作指针
LNode *ra = A; //ra指向A的A的尾结点
while (p != NULL){
ra -> next = p; //将*p链到A的末尾
ra = p;
p = p -> next; //头插后,*p将断链,因此用q以及*p的后继
q = p -> next; //将*p插入到B
p -> next = B -> next;
B -> next = p;
p = q;
}
ra -> next = NULL; //A尾结点的next域置空
return B;
}
题12
12.在一个递增有序的线性表中,有数值相同的元素存在.若存储方式为单链表,
设计算法去掉数值相同的元素,使表中不再有重复的元素.例如(7,10,10,21,30,42,42,42,51,70)
将变作(7,10,21,30,42,51,70)
void Del_Same(LinkList &L){
LNode *p = L -> next,*q;
if (p== NULL)
return ;
while (p->next != NULL) {
q = p -> next; //q指向*p的后继结点
if (p -> data == q -> data){ //找到重复值的结点
p -> next = q -> next; //释放*q结点
free(q); //释放相同元素值的结点
} else {
p = p -> next;
}
}
}
题13
13.假设有两个按元素值递增次序排列的线性表,均以单链表存储.
请编写算法将这两个单链表归并为一个按元素值递减次序排列的单链表,并要求排列原来两个单链表
的结点存放归并后的单链表.
//思想;两个链表已经按元素值递增次序排序,将其合并时,均从第一个结点起进行比较,将小的结点链入
//链表之中,同时后移工作指针.要求结果链表按元素递减次序排列,因此新链表的建立方式应该用头插法
//比较结束后,可能会有一个链表非空,此时用头插法将剩下的结点依次插入新链表即可
void MergeList(LinkList &La, LinkList &Lb){
//合并两个递增有序的链表,并使合并后的链表递减排列
LNode *r,*pa = LA -> next,*pb = Lb -> next;
La -> next =NULL;
while (pa && pb){
if (pa -> pb <= pb-){
r = pa-> next;
pa -> next = La -> next;
La -> next = pa;
pa = r;
}
else {
r = pb -> next;
pb -> next = La -> next;
La -> next = pb
}
}
}
题14
14.设A和B是两个单链表(带头结点),其中元素递增有序.设计一个算法从A和B中公共元素产生单链表C,
要求不破坏A,B的结点.
//思想:表A,B都有序,可以从第一个元素起依次比较A,B两个表的元素,若元素值不等,
//则值小的指针往后移动,若元素值相等,则创建一个值等于两个结点的元素值的新的结点,
//使用尾插法插入到新的链表,并且两个原表指针往后移动一位.直到其中一个链表遍历到表尾.
void Get_Common(LinkList A,LinkList B){
LNode *p = A -> next,*q = B -> next,*r,*s;
LinkList C = (LinkList)malloc(sizeof(LNode));//建立表C
r = C; //r始终指向C的尾结点
//循环跳出条件
while (p != NULL && q != NULL){
if (p -> data < q -> data)
p = p -> next; //若A的当前元素较小,后移指针
else if (p->data > q->data)
q = q -> next; //若B的当前较小,后移较小
else { //找到公共元素结点
s = (LNode*)malloc(sizeof(LNode)); //复制产生结点*s
s -> data = p -> data; //将*s链接到C上
r -> next = s;
r = s;
p = p -> next; //表A和表B继续向后扫描
q = q -> next;
}
r -> next = NULL; //置C尾结点指针为空
}
}
题15
15.已知两个链表A和B分别表示两个集合,其元素递增排列.编制函数,求A与B的交集,并存放于A链表中.
LinkList Union(LinkList &La, LinkList &Lb){
pa = La -> next; //设工作指针分别为pa和pb
pb = Lb -> next;
pc = La; //结果表中当前合并结点的前驱指针
while (La && Lb){
if (pa -> data == pb-> data){//交集并入结果表
pc -> next = pa; //A中结点链接到结果表
pc = pa;
pa = pa -> next;
u = pb; //B中结点释放
pb = pb -> next;
free(u);
} else if (pa->data < pb->data){//若B中当前结点值小于A中当前结点值
u = pa;
pb = pb -> next;//后移指针
free(u);//释放B中当前结点
}
}//while
while (pa){//B已遍历完,A未完
u = pb;
pb = pb -> next;
free(u);//释放A中剩余结点
}
while (pb){//A遍历完,B未完
u = pb;
pb = pb -> next;
free(u);//释放B中剩余结点
}
pc -> next = NULL;//置结果链表指针为null
free(Lb);//释放B表的头结点
return La;
}
题16
16.两个整数序列A=a1,a2,a3,..,am和B=b1,b2,b3,...,bn已经存入两个单链表中.
设计一个算法,判断序列B是否是序列A的连续子序列.
int Pattern(LinkList A, LinkList B){
//A和B分别是数据域为整数的单链表,本算法判断B是否是A的子序列
LNode *p = A;//p为A链表的工作指针,假定A,B均无头结点
LNode *pre = p;//pre记住每趟比较中A链表的开始结点
LNode *q = B;//q是B链表的工作指针
while (p && q)
if (p->data == q->data){//结点值相同
p = p -> next;
q = q -> next;
} else [
pre = pre -> next;
p = pre;//A链表新的开始比较结点
q = B;//q从B链表第一个结点开始
]
if (q == NULL)//B已经比较结束
return 1;//说明B是A的序列
else
return 0;//B不是A的序列
}
题17
17.设计一个算法用于判断带头结点的循环双链表是否对称
int Symmetry(DLinkList L){
//从两头扫描循环双链表,以判断链表是否对称
DNode *p = L->next,*q = L->prior;//两头工作指针
while (p != q && q->next != p)//循环跳出条件
if (p->data==q->data){//所指结点值相同则继续比较
p = p -> next;
q = q -> prior;
}
else //否则,返回0
return 0;
return 1;//比较结束,返回1
}
题18
18.有两个循环单链表,链表的头指针分别为h1和h2,编写一个函数将链表h2链接到链表h2之后,
要求链接后的链表仍保持循环链表形式.
LinkList Link(LinkList &h1, LinkList &h2){
LNode *p,*q;//分别指向两个链表的尾结点
p = h1;
while (p->next != h1)//寻找h1的尾结点
p = p -> next;
q = h2;
while (q -> next != h2)//寻找h2的尾结点
q = q -> next;
p -> next = h2;//将h2链接到h1之后
q -> next = h1;//令h2的尾结点指向h1
return h1;
}
题19
19.设有一个带头结点的循环单链表,其结点值均为正整数.设计一个算法,反复找出单链表中结点值
最小的结点并输出,然后将该结点从中删除,直到单链表空为止,再删除表头结点.
void Del_All(LInkList &L){
//每次删除循环单链表中的最小元素,直到链表空为止
LNode *p,*pre,*minp,*minpre;
while (L->next != L){ //表不空
p = L->next; //p为工作指针
pre = L; //pre指向其前驱
minp = p;minpre = pre; //minp指向最小值结点
while (p != L){
if (p->data < minp->data){
minp = p; //找到值更小
minpre = pre;
}
pre = p; //查找下一个结点
p = p -> next;
}
printf("%d",minp->data);//输出最小值结点元素
minpre->next = minp->next;//最小值结点从表断开
free(minp)l //释放空间
}
free(L); //释放头结点
}
题20
20.设头指针为L的带有表头指针的非循环双向链表,其每个结点中除有pred(前驱指针),data(数据)
和next(后继指针)域外,还有一个访问频度域freq.在链表被启用前,其值均初始化为0.
每当在链表中进行依次Locate(L,x)运算时,令元素值为x的结点中freq域的值增1,并使此链表中结点
保持按访问频度非增(递减)的顺序排列,同时最近访问的结点排在频度相同的结点的前面,以便使频繁访问
的结点总是靠近表头.试编写符合上述要求的Locate(L,x)运算的算法,该运算为函数过程,返回找到
结点的地址,类型为指针型.
DLinkList Locate(DLinkList &L, ElemType x){
//先查找数据x,查找成功时结点的访问频度域+1
//最后将该结点按频度递减插入链表中适当位置(同频度最近访问在前面)
DNode *p = L->next,*q;
while (p && p->data != x)
p = p -> next;//查找值为x的结点
if (!p){
exit(0);
}
else {
p -> freq++; //令元素值为x的结点的freq域+1
p -> next -> pred = p -> pred;
p -> pred -> pred = p -> next;//将p结点从链表上摘下
q = p -> pred; //以下查找p结点的插入位置
while (q != L && q -> freq)
q = q -> pred;
p -> next = q -> next;
q -> next -> pred = p; //将p结点插入,一定是排在同频率的第一个
p -> pred = q;
q -> next = p;
}
return p; //返回值为x的结点的指针
}
栈
栈的数据结构
顺序栈的数据结构描述
#define MaxSize 50
typedef struct { //定义栈中元素的最大个数
ElemType data[MaxSize];//存放栈中元素
int top; //栈顶指针
} SqStack;
链栈的数据结构描述
typedef struct Linknode{
ElemType data; //数据域
struct Linknode *next; //指针域
} *LiStack; //栈类型定义
综合应用题
题1
1.设单链表的表头指针为L,结点结构由data和next两个域构成,其中data域为字符型.试着设计算法判断
该链表的全部n个字符是否中心对称.例如xyx,xyyx都是中心对称.struct
//思想:
//使用栈来判断链表中的数据是否中心对称.将链表的前一半元素依次进栈
//在处理链表的后一半时,当访问到链表的一个元素后,就从栈中弹出一个元素,两个元素比较.
//若相等,则将链表中下一个元素与栈中再弹出的元素比较,直至链表到尾.
//这时若栈是空栈,则得出链表中心对称的结论.
//否则,当链表中的一个元素与栈中弹出元素不等时,结论为链表非中心对称,程序终止.
int dc(LinkList L,int n){
//判断链表中心是否中心对称
int i;
char s[n/2]; //s字符栈
p = L -> next; //p是链表的工作指针,指向待处理的当前元素
for (i = 0; i < n/2; i++){
s[i] = p -> data;
p = p -> next;
}
i--; //恢复最后的i值
if (n%2 == 1) //若n为奇数,后移过中心结点
p = p -> next;
//检测是否中心对称
while (p!=NULL && s[i] == p -> data){
i--; //i充当栈顶指针
p = p -> next;
}
if (i == -1) //栈为空栈
return 1; //链表中心对称
else
return 0; //链表不中心对称
}
题2
2.设有两个栈s1,s2都采用顺序栈方式,并且共享一个存储区[0,...,maxsize-1]为了尽量利用空间,
减少溢出的可能,可采用栈顶相向,迎面增长的存储方式.试着设计s1,s2有关入栈和出栈的操作算法.
//思想:两个栈共享向量空间,将两个栈的栈底设在向量两端,初始时,s1栈顶指针为-1,
//s2栈顶指针为maxsize.两个栈顶指针是否相邻时为栈满.
//两个栈顶相向,迎面增长,栈顶指针指向栈顶元素.
#define maxsize 100 //两个栈共享顺序存储空间所能达到的最多元素数
#define elemtp int //初始化为100
typedef struct
{
elemtp stack[maxsize]; //栈空间
int top[2]; //top为两个栈顶指针
} stk;
stk s; //s是如上定义的结构类型变量,为全局变量
(1)入栈操作
int push(int i, elemtp x){
//入栈成功返回1,否则返回0
if (i < 0 || i > 1){
printf("栈号输入不对\n");
exit(0);
}
if (s.top[1] - s.top[0] == 1){
printf("栈已满\n");
return 0;
}
switch(i){
case 0:s.stack[++s.top[0]] = x;
return 1;
break;
case 1:s.stack[--s.top[1]] = x;
return 1
}
}
(2)退栈操作
elemtp pop(int i){
//i代表栈号,i=0时为s1栈,i=1时为s2栈
//入栈成功返回1,否则返回0
if (i<0||i>1){
printf("栈号输入错误\n");
exit(0);
}
switch ((i))
{
case 0:
if (s.top[0] == 1){
printf("栈空\n");
return -1;
} else {
return s.stack[s.top[0]--]
}
case 1:
if (s.top[1] == maxsize){
printf("栈空\n");
return -1;
} else {
return s.stack[s.top[1]++];
}
}//switch
}
队列
队列的顺序存储类型描述:
#define MaxSize 50 //定义队列中元素的最大个数
typedef struct{
ElemType data[MaxSize]//存放队列元素
int front,rear;//队头指针和队尾指针
} SeQueue;
队列的链式存储
typedef struct { //链式队列结点
ElemType data;
struct LinkNode *next;
} LinkNode;
typedef struct{ //链式队列
LinkNode *front, *rear;//队列的队头和队尾指针
} LinkQueue;
程序设计题
题1
1.如果希望循环队列中的元素都能得到利用,则需设置一个标志域tag,并以tag的值为0或者1
来区分队头指针front和队尾指针rear相同时的队列状态是"空"还是"满",试着编写与此结构相应
的入队和出队算法.
//思想:在循环队列的类型结构中,增设一个tag的整型变量,进队时置tag为1.
//出队时置tag为0(因为只有入队操作可能导致队满,也只有出队操作可能导致队空)
//队列Q初始时,置tag=0,front=rear=0
队列的四要素为如下:
队空条件:Q.front == Q.rear 且 Q.tag == 0
队满条件:Q.front == Q.rear 且 Q.tag == 1
进队操作:
Q.data[Q.rear] = x;
Q.rear = (Q.rear+1)%MaxSize;
Q.tag = 1
出队操作:
x = data[Q.front];
Q.front = (Q.front+1)%MaxSize;
Q.tag = 0
设置"tag"的循环队列入队算法
int EnQueue1(SqQueue &Q, ElemType x){
if (Q.front == Q.rear & =& tag == 1){
return 0;
}
Q.data[Q.rear] = x;
Q.rear = (Q.rear+1)%MaxSize;
Q.tag = 1;
return 1;
}
设置"tag"的循环队列出队算法
int DeQueue(SeQueue &Q, ElemType &x){
if (Q.front == Q && Q.tag == 0){
return 0;
}
x = Q.data[Q.front];
Q.front = (Q.front+1)%MaxSize;
Q.tag = 0;
return 1;
}
题2
2.Q是队列,S是一个空栈,实现将队列中的的元素逆置的算法.
void Inverser(Stack S,Queue Q){
while (!QueueEmpty(Q)){
x = DeQueue(Q); //队列中全部元素依次出队
PUsh(S,x); //元素依次入栈
}
while(!StackEmpty(S)){
Pop(S,x); //栈中全部元素依次出站
EnQueue(Q,x); //再入队
}
}
题3
3.利用两个栈S1,S2来模拟一个队列,已知栈的4个运算定义如下:
Push(S,x); //元素x入栈s
Pop(S,x); //S出栈并将出栈的值赋给x
StackEmpty(S); //判断栈是否为空
StackOverflow(S); //判断栈是否满
那么如何利用栈的运算来实现该队列的3个运算(形参由读者根据要求自己设计)
Enqueue; //将元素x人队
Dequeue; //出队,并将出队元素存储在x中
QueueEmpty; //判断队列是否为空
解:
//入队算法
int EnQueue(Stack &S, Stack &S2, ElemType e){
if (!StackOverflow(S1)){
Push(S1,e);
return 1;
}
if (StackOverflow(S1) && !StackEmpty(S2)){
printf("队列满\n");
return 0;
}
if (StackOverflow(S1) && StackEmpty(S2)){
while (!StackEmpty(S1)){
POP(S1,x);
Push(S2,x);
}
}
Push)(S1,e);
return 1;
}
//出队算法
void DeQueue(STack &s1,Stack &S2, ElemType &x){
if (!StackEmpty(S2)){
Pop(S2,x);
} else if (StackEmpty(S1)){
printf("队列为空\n");
} else {
while (!StackEmpty(S1)){
Pop(S1,x);
Push(S2,x);
}
Pop(S2,x);
}
}
//判断队列为空的算法
int QueueEmpty(Stack S1,Stack S2) {
if (StackEmpty(S1) && StackEmpty(S2))
return 1;
else
return 0;
}
程序设计题
题1
1.假设一个算术表达式中包含圆括号,方括号和花括号三种类型的括号,编写一个算法来判别
表达式中的括号是否配对,以字符"\0"作为算术表达式的结束符.
bool BracketsCheck(char *str){
InitStack(S);
int i = 0;
while (str[i] != '\0'){
switch (str[i]) {
case '(': Push(S,'('); break;
case "[": Push(S,'['); break;
case '{': Push(S,'{'); break;
case ")": Pop(S,e):
if (e != '(') return false;
break;
case ']': Pop(S,e);
if (e != '[') return false;
break;
case '}': Pop(S,e);
if (e != '{') return false;
break;
default: break;
}//switch
i++;
}//while
if (!IsEmpty(S)){
printf("括号不匹配\n");
return false;
} else {
printf("括号匹配\n");
return true;
}
}
题2
2.利用一个栈实现以下递归函数的非递归计算:
Pn(x) = {
1, n = 0
2, n = 1
2xPn-1(x)-2(n-1)Pn-2(x) n > 1
}
//思想:设置一个栈用于保存n和对应的Pn(x)值,栈中相似元素的Pn(x)有体重关系
//然后边处栈计算Pn(x),栈空后该值就计算出来了.
double p(int n, double x){
struct stack{
int no;//保存n
double val;//保存Pn(x)值
} st[MaxSize];
int top = -1,i;//top为栈st的下标值变量
double fv1 = 1,fv2 = 2*x;//n=0,n=1时的初值
for (i = n; i >= 2; i--){
top++;
st[top].no = i
}//入栈
while (top >= 0){
st[top].val = 2*x*fv2-2*(st[top].no-1)*fv1;
fv1 = fv2;
fv2 = st[top].val;
top--;//出栈
}
if (n==0){
return fv1;
}
return fv2;
}
题3
3.某汽车轮渡口,过江渡船每次能载10辆车过江.过江车辆分为客车类和火车类,
上渡船有如下规定:同类车先到先上船;客车先于货车上渡船,且每上4辆客车,
才允许放在一辆货车;若等待客车不足4辆,则以货车代替;若无货车等待,允许客车都上传.
试设计一个算法模拟渡口管理.
//思想:假设数组Q的最大下标为10,恰好是每次载渡的最大量
//假设客车的队列为Q1,货车的队列为Q2.若Q1充足,则每取4个Q1元素后再取一个Q2元素,
//直到Q的长度为10.若Q1不充足,则直接用Q2补足.
Queue q; //过江载船载渡队列
Queue q1; //客车队列
Queue q2; //货车队列
void manager(){
int i = 0, j = 0; //j表示渡船上的总车辆数
while (j < 10){ //不足10辆时
if (!QueueEmpty(q1) && i < 4){//客车队列不空,则未上足4辆
DeQueue(q1,x);//从客车队列出队
EnQueue(q,x);//客车上渡船
i++;//客车数+1
j++;//渡船上的总车辆数+1
} else if (i == 4 && !QueueEmpty(q2)){//客已上足4辆
DeQueue(q2,x);//从货车队列出队
EnQueue(q,x);//货车上渡船
j++;//渡船上的总车辆数+1
i = 0;//每上一辆货车,i重新计数
}
else {//其他情况(客车队列空或货车队列空)
while (j < 10 && i < 4 && !QueueEmpty(q2){//客车队列出队
DeQueue(q2,x);//从货车队列出队
EnQueue(q,x);//货车上渡船
i++;//i计数,当i>4时,退出循环
j++;//渡船上的总车辆数+1
}
i = 0;
}
if (QueueEmpty(q1) && QueueEmpty(q2))
j = 11;//如果货车和客车加起来不足10辆
}
}
总结:
顺序栈的操作
(1)声明一个栈并初始化
Elemtype Stack[maxsize];
int top = -1;
(2)元素进栈
Stack[++top] = x;
(3)元素x出栈
x = Stack[top--];
二叉树
二叉树的链式存储描述
[lchild][data][rchild]
typedef struct BiTNode{
ElemType data; //数据域
struct BiTNode *lchild,*rchild; //左,右指针
} BiTNode,*BiTree;
程序设计题
题1
1.已知一棵二叉树按顺序存储结构进行存储.设计一个算法,求编号分别为i和j的两个结点
的最近的公共祖先结点的值.
//首先必须明确二叉树中任意两个结点必然存在最近的公共祖先结点
//最坏的情况是根结点(两个结点分别在根结点的左右分支中),
//而且从最近的公共祖先结点到根结点的全部祖先结点都是公共的
//由二叉树顺序存储的性质可知,任一结点i的双亲结点的编号为i/2.求解i和j最近
//公共祖先结点的算法步骤如下(设从数组下标1开始存储)
(1)若i>j,则结点i所在层次大于或等于结点j所在层次.
结点i的双亲结点为结点i/2,若i/2=j,则结点i/2是原结点i和结点j的最近公共祖先结点,
若i/2!=j,则令i=i/2,即以该结点i的双亲结点为起点,采用递归的方法继续查找.
(2)若j>i,则结点j所在层次的大于或等于结点i所在层次.
结点j的双亲结点为结点j/2,若j/2=i,则结点j/2是原结点i和结点j的最近公共祖先结点
若j/2!=i,则令j=j/2.
重复上述过程,直到找到它们最近的公共祖先结点为止
ElemType Comm_Ancestor(SqTree T,int i,int j){
//在二叉树中查找结点i和结点j的最近公共祖先结点
if (T(i)!= '#' && T[j] != '#'){//结点存在
while (i!=j){ //两个编号不同时循环
if (i > j)
i = i / 2; //向上找i的祖先
else
j = j / 2; //向上找j的祖先
}
return T[i];
}
}
线索二叉树的存储结构描述:
typedef struct ThreadNode{
ElemType data; //数据元素
struct ThreadNode *lchild,*rchild; //左右孩子指针
int ltag,rtag; //左右线索标志
} ThreadNode,*ThreadTree;
程序设计题
题1
1.编写后序遍历二叉树的非递归算法
//思想;因为后续非递归遍历二叉树的顺序是先访问左子树,再访问右子树,最后访问根结点.
//当堆栈来存储结点,必须分清返回根结点时,是从左子树返回的,还是从右子树返回的.
//因此使用辅助指针r,其指向最近访问过的结点.
//也可以在结点中增加一个标志域,记录是否已被访问
void PostOrder(BiTree T){
InitStack(B);
p = T;
r = NULL;
while (p || !IsEmpty(S)){//走到最最左边
if (p){
push(S,p);
p = p -> lchild;
}
else {//向右
GetTop(S,p);//取栈顶结点
if (p->rchild && p->rchild != r){//如果右子树纯在,且未被访问过
p = p -> rchild;//转向右
push(S,p);//压入栈
p = p -> lchild;//再走到最左
}
else {//否则,弹出结点并返回
pop(S,p);//将结点弹出
visit(p->data);//访问该结点
r = p;//记录最近访问过的结点
p = NULL;//结点访问完后,重置p指针
}
}//else
}//while
}
题2
2.给出二叉树的自下而上,从右到左的层次遍历算法.
//思想:一般的二叉树层次遍历是自上而下,从左到右的遍历.
//利用原有的层次遍历算法,出队的同时将各个结点指针入栈,在所有结点入栈后,
//再从栈顶开始依次访问即是所求的算法.具体实现为:
(1)把根结点入队列
(2)把一个元素出队列,遍历这个元素
(3)依次把这个元素的右孩子,左孩子入队列
(4)若队列不空,则跳到(2),否则结束
void InvertLevel(BiTree bt){
Stack s;
Queue Q;
if (bt != NULL){
InitStack(s);//初始化栈,栈中存放二叉树结点的指针
InitQueue(Q);//初始化队列,队列中存放二叉树结点的指针
EnQueue(Q,bt);
while (IsEmpty(Q) == false){//自上而下层次遍历
DeQueue(Q,p);
Push(s,p);//出队,入栈
if (p -> lchild)//若左孩子不空,则队列
EnQueue(Q,p->lchild);
if (p -> rchild)//若右孩子不空,则入队列
EnQueue(Q,p->rchild)
}
while (IsEmpty(s) == false){
Pop(s,p);
visit(p->data);//自下而上,从右到左的层次遍历
}
}//if结束
}
题3
3.假设二叉树采用二叉链表存储结构,设计一个非递归算法求二叉树的高度
//采用层次遍历的算法,设置遍历level记录当前结点所在层数,设置变量last
//指向当前层最右结点,每次层次遍历出队时与last指针比较,若指针相等,则层数+1
//让last指向下一层最右结点,至少遍历完成.level的值就是二叉树的高度
int Btdepth(BiTree T){
//采用层次遍历的非递归方法求解二叉树的高度
if (!T) return 0;//树空,高度为0
int front = -1, rear = -1;
int last = 0, level = 0;//last指向下一层第一个结点的位置
BiTree Q[MaxSize];//设置队列Q,元素是二叉树结点指针且容易足够
Q[++rear] = T;//将根结点入队
BiTree p;
while (front < rear){//队不空,则循环
p = Q[++front];//队列元素出队,即正在访问的结点
if (p -> lchild)//左孩子入队
Q[++rear] = p -> lchild;
if (p -> rchild)//右孩子入队
Q[++rear] = p -> rchild;
if (front == last){//处理该层的最右结点
level++;//层数增1
last = rear;//last指向下层
}
}
return level;
}
//递归算法
int Btdepth2(BiTree T){
if (T == NULL) //空树
return 0;
ldep = Btdepth(T->lchild);//左子树高度
rdep = Btdepth(T->rchild);//右子树高度
//树的高度为子树最大高度加根结点
if (ldep > rdep)
return ldep + 1;
else
return rdep + 1;
}
题4
4.设一棵二叉树中各结点的值互不相同,其先序遍历序列和中序遍历序列分别存于两个一维数组
A[1..n]和B[1..n]中,试编写算法建立该二叉树的二叉链表.
//思想:由先序序列和中序序列可以唯一确定一棵二叉树.
算法实现步骤如下:
(1)根据先序序列确定树的根结点
(2)根据根结点在中序序列中划分出二叉树的左右子树包含那个结点.
然后根据左右子树结点在先序序列中的的次序可以确定子树的根结点.
BiTree PreInCreate(ElemType A[],ElemType B[], int l1, int h1, int l2, int h2){
//l1,h1为先序的第一个和最后一个结点下标
//l2,h2为中序的第一和最后一个结点下标
root = (BiTNode*)malloc(sizeof(BiTNode)); //建根结点
root -> data = A[11]; //根结点
for (i = l2; B[i] != root -> data; i++) //根结点在中序序列中的划分
llen = i - 12; //左子树长度
rlen = h2 - i; //右子树
if (llen) //递归建立左子树
root -> lchild = PreInCreate(A,B,l1+1,l1+llen,l2,l2+llen-1);
else //左子树为空
root -> lchild = NULL;
if (rlen) //递归建立右子树
root -> rchild = PreInCreate(A,B,h1-rlen+1,h1,h2-rlen+1,h2);
else //右子树为空
root -> rchild = NULL;
return root; //返回根结点指针
}
题5
5.二叉树按二叉链表形式存储,写一个判别给定二叉树是否是完全二叉树的算法
//思想:根据完全二叉树的定义,具有n个结点的完全二叉树与满二叉树中编号从1-n的结点一一对应
//采用层次遍历的算法,将所有结点加入队列(包括空结点).
//当遇到空结点时,查看其后是否有非空结点,若有,则二叉树不是完全二叉树
bool IsComplete(BiTree T){
//判定给定二叉树是否为完全二叉树
InitQueue(Q);
if (!T) //空树为满二叉树
return 1;
EnQueue(Q,T);
while (!IsEmpty(Q)){
DeQueue(Q,p);
if (p){ //结点非空,将其左右子树入队列
DeQueue(Q,p -> lchild);
EnQueue(Q,p -> rchild);
}
else //结点非空,检查其后是否有非空结点
while (!IsEmpty(Q)){
DeQueue(Q,p);
if (p) //结点非空,则二叉树为非完全二叉树
return 0;
}
}
return 1;
}
题6
6.假设二叉树采用二叉链表存储结构存储,试设计一个算法,计算一棵给定二叉树的所有双分支结点个数
//递归模型如下:
f(b) = 0;
f(b) = f(b->lchild) + f(b->rchild)+1
f(b) = f(b->lchild) + f(b->rchild)
int DsonNodes(BiTree b){
if (b == NULL)
return 0;
else if (b->lchild != NULL && b -> rchild != NULL)
return DSonNodes(b->lchild) + DsonNodes(b->rchild)+1;
else
return DsonNodes(b->lchild) + DsonNodes(b->rchild);
}
题7
7.假设二叉树采用二叉链表存储结构存储,设计一个算法,求先序遍历序列中第k(1<k<二叉树中
结点个数)个结点的值
//思想:采用递归算法实现交换二叉树的左右子树,首先交换b结点的左孩子的左右子树
//然后交换b结点的右孩子的左右子树
//最后交换b结点的左右孩子
//当结点为空时递归结束(后序遍历)
void swap(BiTree b){
if (b){
swap (b -> lchild);//递归地交换左子树
swap (b -> rchild);//递归地交换右子树
temp = b -> lchild;//交换左右孩子结点
b -> lchild = b -> rchild;
b -> rchild = temp;
}
}
题8
8.设一棵二叉树的结点结构为(LLINK,INFO,RLINK),ROOT为指向该二叉树根结点的指针,
p和q分别为指向该二叉树中任意两个结点的指针,试着编写算法ANCESTOP(ROOT,p,q,r)
该算法找到p和q的最近公共祖先结点r.
//思想:设置一个全局变量i记录已访问过的结点的序号,其初值是根结点在先序序列中的序号,即为1
//当二叉树b为空时返回特殊字符'#',当i = k时,表示找到了满足条件的结点,返回b->data
//当i!=k时,递归地在左子树中查找,若找到了则返回该值,否则继续递归地在右子树中查找并返回其结果
int i = 1;//遍历序号的全局变量
ElemType PreNode(BiTree b, int k){
if (b == NULL) return '#';//空结点,则返回特殊字符
if (i == k) return b->data;//相等,则当前结点即为第k个结点
i++; //下一个结点
ch = PreNode(b->lchild,k); //左子树中递归寻找
if (ch != '#') //在左子树中,则返回该值
return ch;
ch = PreNode(b->rchild,k); //在右子树中递归寻找
return ch;
}
题9
9.假设二叉树采用二叉链表存储结构,设计一个算法,求非空二叉树b的宽度(即具有结点数最多
的那一层的结点个数)
//思想:采用层次遍历的方法求出所有结点的层次,并将所有结点和对应的层次放在一个队列中.
//然后通过扫描队列求出各层的结点总数,最大的层结点总数即为二叉树的宽度
typedef struct{
BiTree data[MaxSize];
int level[MaxSize];
int front,rear;
}Qu;
int BTWidth(BiTree b){
BiTree p;
int k,max,i,n;
Qu.front = Qu.rear = -1;//队列为空
Qu.rear++;
Qu.data[Qu.rear] = b; //根结点指针入队
Qu.level[Qu.rear] = 1; //根结点层次为1
while (Qu.front < Qu.rear){
Qu.front++; //出队
p = Qu.data[Qu.front]; //出队结点
k = Qu.level[Qu.front]; //出队结点的层次
if (p -> lchild != NULL){ //左孩子进队列
Qu.rear++;
Qu.data[Qu.rear] = p -> lchild;
Qu.level[Qu.rear] = k+1;
}
if (p->rchild != NULL){//右孩子进队列
Qu.rear++;
Qu.data[Qu.rear] = p -> rchild;
Qu.level[Qu.rear] = k+1;
}
}//while
max = 0;i = 0; //max保存同一层最多的结点个数
k = 1; //k表示从第一层开始查找
while (i < Qu.rear){ //i扫描队中所有元素
n = 0; //n统计第k层的结点个数
while (i <= Qu.rear && Qu.level[i] == k){
n++;
i++;
}
k = Qu.level[i];
if (n > max) max = n;//保存最大的n
}
return max;
}
题10
10.设有一棵满二叉树(所有结点值均不同),已知其先序序列为pre,设计一个算法求其后序序列post
void PreToPost(ElemType pre[], int l1, int h1, ElemType post[], int l2, int h2){
int half;
if (h1 >= l1){
post[h2] = pre[l1];
half = (h1-l1)/2;
PreToPost(Pre,l1+1,l1+half,post,l2,l2+half-1);//转换左子树
PreToPost(Pre,l1+half+1,h1,post,l2+half,h2-1);//转换右子树
}
}
题11
11.设计一个算法将二叉树的叶结点按从左到右的顺序连成一个单链表,表头指针为head.
二叉树按二叉链表方式存储,链接时用叶子结点的右指针域来存放单链表指针.
//思想:设置前驱结点指针pre,初始为空
//第一个叶结点由指针head指向,遍历到叶结时,就将它前驱的rchild指针指向它
//最后一个叶结点的rchild为空
LinkedList head,pre = NULL;//全局变量
LinkedList InOrder(BiTree bt){
if (bt){
InOrder(bt -> lchild);//中序遍历左子树
if (bt -> lchild == NULL && bt -> rchild == NULL)//叶结点
if (pre == NULL){
head = bt;
pre = bt;
}//处理第一个叶结点
else {
pre -> rchild = bt;
pre = bt;
}//将叶结点链入链表
InOrder(bt->rchild); //中序遍历右子树
pre -> rchild = NULL;//设置链表尾
}
return head;
}
题12
12.试设计判断两棵二叉树是否相似的算法.所谓二叉树T1和T2都是空的二叉树或都只有一个根
结点:或T1的左子树和T2的左子树都是相似的.且T1的右子树和T2的右子树都是相似的.
//采用递归的思想求解,若都是空树,则相似
//若有一个空另一个不为空,则不相似
//否则递归地比较两个树的左右子树是否相似
(1)f(T1,T2)=1;若T1=T2=NULL
(2)f(T1,T2)= 0;若一个空,一个不为空
(3)f(T1,T2) = f(T1->lchild,T-lchild) && f(T1->rchild,T2->rchild)
int similar(BiTree T1,BiTree T2)}{
int leftS, rightS;
//两树皆空
if (T1 == NULL && T2 == NULL)
return 1;
//其中一树为空
else if (T1 == NULL || T2 == NULL)
return 0;
else {
//递归判断
leftS = similar(T1->lchild, T2->lchild);
rightS = similar(T1->rchild, T2->rchild);
return lefts && rightS;
}
}
题13
13.写出在中序线索二叉树里查找指定结点在后序的前驱结点的算法.
//思想:在后序序列中,若结点p有右子女,则有右子女是其前驱
//若无右子女而有左子女,则左子女是其前驱
//若结点p左右子女均无,设其中序左线索指向某祖先结点f(p是f右子树中按照
//中序遍历的第一个结点),若f有左子女,则其左子女是结点p在后序下的前驱;
//若f无左子女,则顺其前驱找双亲的双亲,一直找到双亲有左子女
//还有一种情况是,若p是中序遍历的第一个结点,结点p在中序和后序下均无前驱
BiThrThree InPostPre(BiThrTree t, BiThrTree p){
BiThrTree q;
if (p->rtag == 0) //若p有右子女,则右子女是其后序前驱
q = p -> rchild;
else if (p -> ltag == 0) //若p只有左子女,左子女是其后序前驱
q = p -> lchild;
else if (p -> lchild == NULL)
q = NULL; //p是中序序列第一结点,无后序前驱
else { //顺左线索向上找p的祖先,若存在,再找祖先的左子女
while (p->ltag == 1 && p -> lchild != NULL)
p = p -> lchild;
if (p -> ltag == 0)
q = p -> lchild;//p结点的祖先的左子女是其后序前驱
else
q = NULL;//仅有单支树(p是叶子),已到根结点,p无后序前驱
}
return q;
}
题14
[2014年计算机联考真题]
14.二叉树的带权路径长度(WPL)是二叉树中所有叶结点的带权路径长度之和.给定一棵二叉树T,
采用二叉链表存储,结点结构为[left][weight][right].
其中叶结点的weight域保存该结点的非负权值.
设root为指向T的根结点的指针,请设计求T的WPL的算法,要求:
(1)给出算法的基本设计思想
(2)使用C或C++语言,给处二叉树结点的数据类型定义
(3)根据设计思想,采用C或C++语言描述算法,关键之处给出注释.
解:
二叉树结点的数据类型定义:
typedef struct BiTNode{
int weight;
struct BiTNode *lchild,*rchild;
} BiTNode,*BiTree;
(1)基于先序遍历的算法
int WPL(BiTree root){
return wpl_PreOrder(root,0);
}
int wpl_PreOrder(BiTree root, int deep){
static int wpl = 0;
//若为叶结点,累积wpl
if (root -> lchild == NULL & root -> rchild == NULL)
wpl += deep*root ->weight;
if (root -> lchild != NULL)//若左子树不空,对左子树递归遍历
wpl_PreOrder(root -> lchild, deep+1);
if (root -> rchild != NULL)//若右子树不空,对右子树递归遍历
wpl_PreOrder(root->rchild,deep+1);
return wpl;
}
(2)基于层次遍历的算法
#define MaxSize 100 //设置队列的最大容量
int wpl_LevelOrder(BiTree root){
BiTree q[MaxSize]; //声明队列,end1为头指针,end2为尾指针
int end1, end2; //队列最多容纳MaxSize-1个元素
end1 = end2 = 0; //头指针指向队头元素,尾巴指针指向队尾的后一个元素
int wpl = 0,deep = 0; //初始化wpl和深度
BiTree lastNode; //lastNode用来记录当前层的最后一个结点
BiTree newlastNode; //newlastNode用来记录下一层的最后一个结点
lastNode = root; //lastNode初始化为根结点
newlastNode = NULL; //newlastNode初始化为空
q[end2++] = root; //根结点入队
while (end1 != end2){ //层次遍历,若队列不空则循环
BiTree t = q[end++]; //拿出队列中的头一个元素
if (t -> lchild == NULL && rchild == NULL){
wpl += deep*t -> weight;
} //若为叶结点,统计wpl
//若非叶结点 把左结点入队
if (t -> lchild == NULL && rchild == NULL){
q[end2++] = t -> lchild;
newlastNode = t->lchild;
}//并设下一层的最后一个结点为该结点的左结点
//处理叶结点
if (t -> rchild != NULL){
q[end2++] = t->rchild;
newlastNode = t->rchild;
}
//若该结点为本层最后一个结点,更新lastNode
if (t == lastNode){
lastNode = newlastNode;
deep+=1;//层数+1
}
}
return wpl;//返回wpl
}
树,森林
双亲表示法的存储结构描述如下:
#define MAX_TREE_SIZE 100 //树中最多结点数
typedef struct{ //树的结点定义
ElemType data; //数据元素
int parent; //双亲位置域
} PTNode;
typedef struct { //树的类型定义
PTNode nodes[MAX_TREE_SIZE]; //双亲定义
int n; //结点数
} PTree;
孩子兄弟表示法的存储结构描述如下:
typedef struct CSNode {
ElemType data; //数据域
struct CSNode *firstchild,*nextsibling;//第一个孩子和右兄弟指针
} CSNode,*CSTree;
程序设计题:
1.编程求以孩子兄弟表示法存储的森林的叶子结点数
//当森林(树)以孩子兄弟表示法存储时,若结点没有孩子(fch=null),则它必是叶子.
//总的叶子结点个数是孩子子树(fch)上的叶子数和兄弟子树(nsib)上叶结点个数之和.
typedef struct node{
ElemType data; //数据域
struct node *fch,*nsib;//孩子与兄弟域
} *Tree;
int Leaves(Tree t){
if (t == NULL)
return 0;
if (t->fch == NULL)
return 1+Leaves(t->nsib);
else
return Leaves(t->fch)+Leaves(t->nsib);
}
2.以孩子兄弟表示法存储的森林的叶子结点数.
//思想:采用递归算法,若树为空,高度为零;
//否则,高度为第一子女树高度加1和兄弟子树高度的大者.
//其非递归算法使用队列,逐层遍历树,取得树的高度
int Height(CSTree bt){
//递归求以孩子兄弟链表表示的树的深度
if (bf == NULL)
return 0;
else{//否则,高度取子女高度+1和兄弟子树高度的大者
hc = height(bt->firstchild);//第一子女树高
hs = height(bt->nextsibling);//兄弟树高
if (hc + 1 > hs)
return hc + 1;
else
return hs;
}
}
3.已知一棵树的层次序列以及每个结点的度,编写一个算法构造此树的孩子-兄弟链表
//思想:设立一个辅助数组pointer[],存储新建树的各结点的地址
//再根据层次序列与每个结点的度,逐个链接结点.
#define maxNodes 15
void createCSTree_Degree(CSTree&T, DataType e[], int degree[], int n){
CSNode *pointer = new CSNode(maxNodes);
int i,j,d,k = 0;//判断pointer[i]为空的语句未写
for (i = 0; i < n; i++){//初始化
pointer[i] = new CSNode;//判断pointer[i]为空的语句未写
pointer[i] -> data = e[i];
pointer[i] -> lchild = pointer[i] -> rsibling = NULL;
}
for (i = 0; i < n; i++){
d = degree[i];//结点i的度数
if (d){
k++;//k为子女结点序号
pointer[i] -> lchild = pointer[k];//建立i与子女k间的链接
for (j = 2; j <= d; j++)
pointer[j-1]->rsibling = pointer[j];
}
}
T = pointer[0];
delete[] pointer;
}
4.试编写一个算法,求出指定结点在给定二叉树是二茬排序树.
//对于二叉排序树来说,其中序遍历序列为一个递增有序序列
//因此,对给定的二叉树进行中序遍历,如果始终能保持前一个值比后一个值小,则证明是二叉排序树
KeyType predt = -32767;//predt为全局变量,保存当前结点中序前驱的值,初值为-∞
int JudgeBST(BiTree bt){
int b1,b2;
if (b1 == NULL)//空树
return 1;
else {
b1 = JudgeBST(bt->lchild);//判断左子树是否是二叉排序树
if (b1 == 0 || predt >= bt -> data)//若左子树返回值为0或者前驱大于等于当前结点
return 0;//则不是二叉排序树
predt = bt -> data;//保存当前的关键字
b2 = JudgeBST(bt->rchild);//判断右边子树
return b2;//返回右子树的结果
}
}
5.设计一个算法,求出指定结点在给定结点二叉排序树中的层次
//思想:设二叉树采用二叉链表存储结构.在二叉排序树中,查找一次就下降一层.
//因此查找该结点所用的次数就是该结点在二叉排序树中的层次.
//采用二叉排序树非递归查找算法,用n保存查找层次,每查找依次,n+1,直到找到相应结点.
int level(BiTree bt,BSTNode *p){
int n = 0; //统计查找次数
BiTree t = bt;
if (bt != NULL){
n++;
while (t -> data != p -> data){//在左子树中查找
if (t -> data < p -> data)
t = t -> rchild;
else //在右子树中查找
t = t -> lchild;
n++;//层次+1
}
}
return m;
}
6.利用二叉树遍历的思想编写一个判断二叉树是否是平衡树的算法.
//思想:设置二叉树的平衡标记balance,以标记返回二叉树bt是否为平衡二叉树
//若为平衡二叉树,则返回1,否则返回0
//h为二叉树bt的高度.采用后序遍历的递归算法
(1)若bt为空,则高度为0,balance = 1
(2)若bt仅有根结点,则高度为1,balance=1
(3)否则,对于bt的左右子树执行递归运算,返回左右子树的高度和平衡标记,
bt的高度为最高子树的高度+1.若左右子树的高度差大于1,则balance=0;
若左右子树的高度差小于等于1,且左右子树都平衡时,balance=1,否则balance=0;
void Judge_AVL(BiTree bt, int &balance, int &h){
//左右子树的平衡标记和高度
int bl = 0, br = 0, hl = 0, hr = 0;
if (bt == NULL){//空树,高度为0
h = 0;
balance = 1;
}
//仅有根结点,则高度为1
else if (bt -> lchild == NULL && bt -> rchild == NULL){
h = 1;
balance = 1;
}
else {
Judge_AVL(bt->lchild,bl,hl);//递归判断左子树
Judge_AVL(bt->rchild,br,hr);//递归判断右子树
h = (h1>hr ? hl:hr)+1;
//若子树高度差的绝对值<2,则看左右子树是否都平衡
if (abs(h1-hr)<2)
balance = bl && br;//左右子树都平衡时,二叉树平衡
else
balance = 0;
}
}
7.设计一个算法,求出给定二叉排序树中最小和最大的关键字
//在一棵二叉排序树中,最左下结点即为关键字最小的结点,最右下结点即为关键字最大的结点
KeyType MinKey(BSTNode *bt){
//求出二叉排序树中最小关键字结点
while (bt -> lchild != NULL)
bt = bt -> lchild;
return bt -> data;
}
KeyType MinKey(BSTNode *bt){
//求出二叉排序树中最大关键字结点
while (bt -> rchild != NULL)
bt = bt -> rchild;
return bt -> data;
}
8.设计一个算法,从大到小输出二叉排序树中所有其值不小于k的关键字.
//根据二叉排序树的性质可知,右子树中所有的结点值均大于根结点值.
//左子树中所有的根结点值均小于根结点.
//为了从大到小输出,先遍历右子树,再访问根结点,后遍历左子树
void OutPut(BSTNode *bt,KeyType k){
if (bt == NULL)
return;
if (bt -> rchild != NULL)
OutPut(bt->rchild,k);//递归输出右子树
if (bt -> data >= k)
printf("%d",bt -> data);//只输出大于等于k的结点值
if (bt -> lchild != NULL)
OutPut(bt -> lchild,k);//递归输出左子树的结点
}
9.编写递归算法,在一棵有n个结点的随机建立起来的二叉排序树上查找第k(1<k<n)小的的元素.
并返回指向该结点的指针.要求算法的平均时间复杂度为O(log2n).二叉排序树的每个结点中除data,
lchild,rchild等数据成员外,增加一个count成员,保存以该结点为根的子树上的结点个数.
BSTNode *Search_small(BSTNode *t, int k){
if (k < 1 || k > t -> count) return NULL;
if (t -> lchild == NULL){
if (k == 1) return t;
else return Search_small(t->rchild,k-1);
}
else {
if (t->lchild->count == k-1) return t;
if (t->lchild->count > k-1) return Search_small(t->lchild,k);
if (t->lchild->count < k-1)
return Search_small(t->rchild,k-(lchild->count+1));
}
}
//最大查找长度取决于树的高度.由于二叉排序树是随机生成的,其高度应该是O(log2n)
//算法时间复杂度为O(log2n).
总结:
(1)遍历的递归程序
void Track(BiTree *p){
if (p != NULL){
//(1)visit(p) => 先序
Track(p->lchild);
//(2)visit(p) => 后序
Track(p->rchild);
//(3)visit(p)
}
}
(2)非递归程序
typedef struct {
BTNode *p; //p是二叉树的结点的指针
int rvisited; //rvisited=1代表p所指向的结点的右结点已被访问过
} SNode; //栈中的结点定义
typedef struct {
SNode Elem[maxsize];
int top;
} SqStack;
void PostOrder(BiTree T){
SNode sn;
BTNode *pt = T;
InitStack(S);
while(T){
Push(pt,);
pt = pt -> lchild;
}
while (!S.IsEmpty()){
sn = S.getTop();
if (sn.p->rchild == NULL || sn.rvisited)}{
Pop(S,pt);
visit(pt);
}
else {
sn.rvisited = ;
pt = sn.p->rchild;
while (pt!= NULL){
Push(S,pt,);
pt = pt -> lchild;
}
}//本轮结束
}//while
}
图
图的邻接矩阵存储结构定义如下
#define MaxVertexNum 100 //顶点数目的最大值
typedef char VertexType; //顶点的数据类型
typedef int EdgeType; //带权图中边上权值的数据类型
typedef struct {
vertexType Vex[MaxVertexNum]; //顶点表
EdgeType Edge[MaxVertexNum][MaxVertexNum];//邻接矩阵,边表
int vexnum,arcnum; //图的当前顶点数和弧数
} MGraph;
图的邻接表存储结构定义如下:
#define MaxVertexNum 100 //图中顶点数目的最大值
typedef struct ArcNode{ //边表结点
int adjvex; //该弧所指向的顶点的位置
struct ArcNode *next; //指向下一条弧的指针
//InfoType info; //网的边权值
} ArcNode;
typedef struct VNode{ //顶点表结点
VectexType data; //顶点信息
ArcNode *first; //指向第一条依附该顶点的弧的指针
} VNode,AdjList[MaxVertexNum];
typedef struct { //邻接表
AdjList vertices; //图的顶点数和弧数
int vexnum,arcnum; //ALGraph是以邻接表存储的图类型
} ALGraph;
图的十字链表存储结构定义如下:
#define MaxVertexNum 100 //图中顶点数目的最大值
typedef struct ArcNode{ //边表结点
int tailvex, headvex; //该弧的头尾结点
struct ArcNode *hlink, *tlink; //分别指向弧头相同和弧尾相同的结点
//InfoType info; //相关信息指针
}
typedef struct VNode{ //顶点表结点
VertexType data; //顶点信息
ArcNode *firstin, *firstout; //指向第一条入弧和出弧
} VNode;
typedef struct {
VNode xlist[MaxVertexNum]; //邻接表
int vexnum,arcnum; //图的顶点数和弧数
} GLGraph; //GLGraph是以十字邻接存储的图类型
图的邻接多重表存储结构定义如下:
#define MaxVertexNum 100 //图中顶点数目的最大值
typedef struct ArcNode{ //边表结点
bool mark; //访问标记
int ivex,jvex; //分别指向该弧的两个结点
struct ArcNode *ilink,*jlink; //分别指向该弧的两个顶点的下一条边
//InfoType info; //相关信息指针
}
typedef struct VNode{ //顶点表结点
VertexType data; //顶点信息
ArcNode *firstedge; //指向第一条依附该顶点的边
} VNode;
typedef struct{
VNode adjmulist[MaxVertexNum]; //邻接表
int vexnum,arcnum; //图的顶点数和弧数
} AMLGraph; //AMLGraph是以邻接多重表存储的图类型
程序设计题
1.写出从图的邻接表表示转换成邻接矩阵表示的算法
//思想:设图的顶点分别存储在v[n]数组中.
//首先初始化邻接表.遍历邻接表,在依次遍历顶点v[i]的边链表时,修改邻接矩阵的第i行的元素值
//若链表边结点的值为j,则置arcs[i][j]=1.
//遍历完邻接表时,整个转换也就结束.
void Convert(ALGraph &G, int arcs[M][N]){
for (int i = 0; i < n; i++){ //依次遍历各顶表结点为头的边链表
p = (G->v[i]).firstarc; //取出顶点i的第一条出边
while (p != NULL){ //遍历边链表
arcs[i][p->data] = 1;
p = p -> nextarc; //取下一条出边
}//while
}//for
}
广度优先搜索(类似二茬树的层序遍历)
bool visited[MAX_VERTEX_NUM]; //访问标记数组
void BFSraverse(Graph G){
for (i = 0; i < G.vexnum; ++i)
visited[i] = FALSE; //访问标记数组初始化
InitQueue(Q); //初始化辅助队列Q
for (i = 0; i < G.vexnum; ++i)//从0号顶点开始遍历
if (!visited[i]) //对每个连通分量调用一次BFS
BFS(G,i); //Vi未访问过,从Vi开始BFS
}
void BFS(Graph G,int v){
//从顶点v出发,广度优先遍历图G,借助一个辅助队列Q
visit(v); //访问初始顶点
visited[v] = TRUE; //v做已访问标记
EnQueue(Q,v); //顶点v入队
while (!isEmpty(Q)){
DeQueue(Q,v); //顶点v出队列
//检测v所有邻接表
for (w = FirstNeighbor(G,v); w>=0; w=NextNeighbor(G,v,w))
if (!visited[w]){ //w为v的尚未访问的邻接顶点
visit(w); //访问顶点w
visited[w] = TRUE; //对w做已访问标记
EnQueue(Q,w); //顶点w入队
}//if
}//while
}
BFS算法求解单源最短路径问题的算法:
void BFS_MIN_Distance(Graph G, int u){
//d[i]表示从u到i结点的最短路径
for (i=0; i < G.vexnum; ++i)//初始化路径长度
d[i] = ∞;
visited[u] = TRUE;
d[u] = 0;
EnQueue(Q,u);
while (!isEmpty(Q)){ //BFS算法主过程
DeQueue(Q,u); //队头元素u出队
for (w = FirstNeighbo(G,u); w>=0; w = NextNeighbor(G,u,w))
if (!visited[w]){ //w为u的尚未访问的邻接顶点
visited[w] = TRUE; //设已访问标记
d[w] = d[u] +1; //路径长度+1
EnQueue(Q,w); //顶点w入队
}//if
}//while
}
深度优先搜索(类似树的先序遍历过程)
bool visited[MAX_VERTEX_NUM]; //访问标记数组
void DFSTraverse(Graph G){
for (v = 0; v < G.vexnum; ++v)
visited[v] = FALSE; //初始化标记数据
for (v = 0; v < G.vexnum; ++v)//从v=0开始遍历
if (!visited[v])
DFS(G,v);
}
void DFS(Graph G, int v){
//采用递归思想,深度优先遍历图G,需要借助栈
visit(v);
visited[v] = TRUE; //设已访问标记
for (w = FirstNeighbor(G,v); w >= 0; w = NextNeighbor(G,v,w));
if (!visited[w]){
DFS(G,w);
}
}
程序设计题
程序1
1.试设计一个算法,判断一个无向图G是否为一棵树.若是一棵树,则算法返回true,否则返回false;
//一个无向图G是一棵树的条件是:G必须是无回路的连通图或有n+1条边的连通图
//这里采用后者作为判断条件,对于连通的判定,可用能否遍历全部顶点来实现.
//可以采用深度优先搜索算法在遍历图的过程中统计可能访问到顶点个数和边的条数
//如果依次遍历就能访问到n个顶点和n-1条边,则判定此图是一棵树
bool visited[MAX_VERTEX_NUM];
bool isTree(Graph &G){
for (i = 1; i <= G.vexnum; i++)
visited[i] = FALSE;
int Vnum = 0, Enum = 0;
DFS(G1,1,Vnum,Enum,visited);
if (VNum == G.vexnum && Enum == 2*(G.vexnum-1))
return true; //符合树的条件
else
return false; //不符合树的条件
}
void DFS(Graph &G, int v,int &Vnum, int& Enum, int visited[]){
visited[v] = TRUE; //作访问标记,顶点计数
Vnum++;
int w = FirstNeighbor(G,v); //取v的第一个邻接顶点
while (w != -1){ //当邻接顶点存储
Enum ++; //边存在,边计数
if (!visited[w]) //当该邻接顶点未访问过
DFS(G,w,Vnum,visited);
w = NextNeighbor(G,v,w);
}
}
程序2
2.写出图的深度优先搜索DFS算法的非递归算法(图采用邻表表形式)
//在深度优先搜索的非递归算法中使用了一个栈S,记忆下一步可能访问的顶点
//同时使用了一个访问标记数组visited[i],在visited[i]中记忆第i个顶点
//是否在栈内.若是,以后它不能再这样进展.
void DFS_Non_RC(AGraph& G, int v){
int w; //顶点序号
InitStack(S); //初始化栈
for (i = 0; i < G.vexnum; i++) //初始化visited
visited[i] = FALSE; //v入栈并置visited[v]
Push(S,v);
visited[v] = TRUE;
while (!IsEmpty(S)){
k = Pop(S); //栈中退出一个顶点
visit(k); //先访问,再将其子结点入栈
//k所有邻接点
for (w = FirstNeighbor(G,k); w >= 0; w = NextNeighbor(G,k,w))
if (!visited[w]){//未进过栈的顶点进栈
Push(S,w);
visited[w] = true; //作标记,以免再次入栈
}//if
}//while
}
程序3
3.分别采用基于深度优先遍历和广度优先遍历算法判别以邻接表方式存储的有向图中是否存在
由顶点vi到vj的路径(i!=j).注意:算法中涉及的图的基本操作必须在此存储结构上实现.
//对于两个不同的遍历算法,都是采用从顶点vi出发,依次遍历图中每个顶点,知道搜索到顶点vj
//如果能够搜索到vj,则说明存在由顶点vi到顶点的路径
int visited[MAXSIZE] = {0};
int Exist_Path_DFS(ALGraph G,int i, int j){
int p;
if (i == j)
return 1;
else {
visited[i] = 1;
for (p = FirstNeighbor(G,i); p >= 0; p = Nextneignbor(G,i,p)){
if (!visited{k} && Exist_Path_DFS(G,p,j))
retur 1
}//for
}//else
return 0;
}
程序4
4.假设图用邻接表表示,设计一个算法,输出从顶点Vi到顶点Vj的所有简单路径.
int visited[MASSIZE] = {0};
int Exist_Path_BFS(ALGraph G, int i, int j){
InitQueue(Q); //初始化辅助队列
EnQueue(Q,i); //顶点i入队
while (!isEmpty(Q)){ //非空循环
DeQueue(Q,u); //队头顶点出队
visited[u] = 1; //置访问标记
//检查所有邻接表
for (p = FirstNeighbor(G,i); p; p= NextNeighbor(G,i,p)){
k = p.adjvex;
if (k == j)//若k==j,则查找成功
return 1;
if (!visit[k])//否则,顶点k入队
EnQueue(Q,K);
}//for
}//while
return 0;
}