时间复杂度
- 下面程序的时间复杂度是 ------ O(m*n)
for(i=0;i<m;i++)
for(j=0;j<n;j++)
a[i][j] = 0;
- 下面程序的时间复杂度是 ------ O(n)
s = 1;
for(i=1;i<n;i++)
s = s * i;
- 下面程序的时间复杂度是 ------ O(n2)
s = 0;
for(i=0;i<n;i++)
for(j=0;j<n;j++)
s += a[i][j];
- 下面程序的时间复杂度是 ------ O(log3n)
i = 1;
while(i <= n)
i = i * 3;
逻辑图和逻辑结构
B = (K, R)
K = {k1, k2, k3, k4, k5, k6, k7, k8, k9}
R = {<k1, k2>, <k1, k3>, <k3, k6>, <k3, k4>, <k4, k5>, <k6, k8>, <k6, k7>, <k8, k9>}
线性表
思路:因为线性表是非递减有序的,所以相同的元素必定会相邻分布。我们只需要按顺序遍历,依次查看后一个元素是否与之相同,若不相同则可以确定该元素不存在相同元素,可以进行下一个元素;反之,则相同,将后一个元素删除。重复操作直到遍历至线性表最后一个。
Status DeleteSame(SqList &L){//从线性表L中删除值相同的多余元素
int i;
while(i <= ListLength(L)-1){
GetElem(L, i, e1);//获取第i位元素e1
GetElem(L, i+1, e2);//获取第i+1位元素e2
if(e1 != e2)//元素不相同,进行下一个
i++;
else//元素相同,删除一个
ListDelete(L, i+1, e);
}
return OK;
}
思想:将i+k-1起的所有元素依次移到i-1起的对应位置上。
i-1 -> i+k-1
Status DeleteK(SqList &L, int i, int k){//在线性表L中删除第i个元素起的k个元素
if(i<0 || k<0 || i+k-1>L.length)//i为逻辑位序
return INFEASIBLE;
for(int j=i+k-1; j<=L.length-1; j++)//将i+k-1起的所有元素依次移到i-1起的对应位置上
L.elem[j-k] = L.elem[j];
L.length = L.length - k;
return OK;
}
思路:区分出奇偶次,进行分别插入。
Status Divide_LinkedPoly(LinkedPoly &L, LinkedPoly &La, LinkedPoly &Lb){
//将单向循环链表存储的稀疏多项式L拆成只含奇次项的La和只含偶次项的Lb
La = L;
Lb = (LinkedPoly)malloc(sizeof(PolyNode));
LinkedPoly p = L->next;
LinkedPoly pa = La;
LinkedPoly pb = Lb;
while(p != L){
if(p->data.exp % 2){//奇次项
pa->next = p;
pa = p;
}
else{//偶次项
pb->next = p;
pb = p;
}
p = p->next;//下一个多项式
}
pa->next = La;//将La首尾相连,成循环链表
pb->next = Lb;//将Lb首尾相连,成循环链表
return OK;
}
思想:按从小到大的顺序依次把 La 和 Lb 的元素插入新表的头部 Lc 处,最后处理 La 或 Lb 的剩余元素。
Status ListMergeOppose_L(LinkList &La, LinkList &Lb, LinkList &Lc){
LinkList pa = La->next;
LinkList pb = Lb->next;//pa和pb分别指向A,B的当前元素
Lc = La;
Lc->next = NULL;
while(pa && pb){
if(pa->data <= pb->data){
p = pa->next;
pa->next = Lc->next;
Lc->next = pa;//将La的元素插入新表的表头
pa = p;
}
else{
p = pb->next;
pb->next = Lc->next;
Lc->next = pb;//将Lb的元素插入新表的表头
pb = p;
}
}
while(pa){
p = pa->next;
pa->next = Lc->next;
Lc->next = pa;//将La的剩余元素插入新表的表头
pa = p;
}
while(pb){
p = pb->next;
pb->next = Lc->next;
Lc->next = pb;//将Lb的剩余元素插入新表的表头
pb = p;
}
free(Lb);
return OK;
}
时间复杂度:O(n)。
Status Delete_Between(LinkList &L, int mink, int maxk){
//删除链表L中值大于mink且小于maxk的所有元素
LinkList p = L, p;
while(p->next->data <= mink)
p = p->next;//p是最后一个不大于mink的元素
if(p->next){//如果还有比mink更大的元素
q = p->next;
while(q->data < maxk){
p->next = q->next;//q的第一个不小于maxk的元素
free(q);
q = p->next;
}
}
return OK;
}
思路:按位比较即可。
Status Compare_SqList(SqList LA, SqList LB){
//比较字符表LA和LB,并用返回表示结果
//值为正,表示LA>LB
//值为负,表示LA<LB
//值为零,表示LA=LB
//设LA和LB中表长以后存储的字符均为空
for(int i=1; LA.elem[i]||LB.elem[i]; i++)
if(LA.elem[i] != LB.elem[i])//出现字符不相同,则可以区分大小
return LA.elem[i] - LB.elem[i];
return 0;
}
栈和队列
ABCD、ABDC、ACBD、ACDB、BACD、ADCB、BADC、BCAD、BCDA、BDCA、CBAD、CBDA、CDBA、DCBA
队列Q采用顺序存储结构,设队头指针front指向队列头元素位置,队尾指针rear指向队列尾元素的下一个位置,队列的容量(即存储的空间大小)为MAXQSIZE。入队时,若Q.rear==MAXQSIZE,则会发生队列的上溢现象,即使队列中尚余有足够的空间,但元素却不能入队,出现了“假溢出”现象。
一般地,可用以下方法解决:
(1)采用移动元素的方法。假定空余空间足够,每当有一个新元素入队,就将队列中已有的元素向队头移动一个位置。
(2)每当删去一个队头元素,则可依次移动队列中的元素总是使front指针指向队列中的第一个位置。
(3)采用循环队列方式。将队头、队尾看作是一个首尾相接的循环队列,此时队首仍在队尾之前,作插入和删除运算时仍遵循“先进先出”的原则。
Stack
char
队列逆置
只设一个指针Q.rear指向队尾元素结点,存储结构的描述与单链队列一致。
Status InitCircleQueue(LinkQueue &Q){
//初始化循环链表表示的队列Q
Q.rear = (QueuePtr)malloc(sizeof(QNode));
if(!Q.rear)
exit(OVERFLOW);
Q.rear->next = Q.rear;//连成循环
}
Status EnCircleQueue(LinkQueue &Q, QElemType e){
//把元素e插入循环链表表示的队列Q
p = (QueuePtr)malloc(sizeof(QNode));
if(!p)
exit(OVERFLOW);
p->data = e;
p->next = Q.rear->next;//直接把p加在Q.rear的后面
Q.rear->next = p;
Q.rear = p;//修改尾指针
}
Status DeCircleQueue(LinkQueue &Q, QElemType &e){
//从循环链表表示的队列Q头部删除元素e
if(Q.rear->next == Q.rear)
return INFEASIBLE;//队列已空
p = Q.rear->next->next;
e = p->data;
Q.rear->next->next = p->next;
if(Q.rear == p)
Q.rear = p->next;
free(p);
return OK;
}
思路:利用栈:后进先出和队列:先进先出的特点,将输入的字符同时入栈和入队列。再进行出栈和出队列进行比较。
int Palindrome_Test(){
//判别字符串是否回文序列,是则返回1,否则返回0
InitStack(S);//初始化栈
InitQueue(Q);//初始化队列
while((c=getchar()) != '@'){
Push(S, c);//进栈
EnQueue(Q, c);//进队列
}
while(!StackEmpty(S)){//利用栈:后进先出;队列:先进先出的特点进行比较
Pop(S, a);
DeQueue(Q, b);
if(a != b)
return ERROR;
}
return OK;
}
数组
链接
(1)按行优先存储:
A[0][0][0][0],A[0][0][0][1],A[0][0][1][0],A[0][0][1][1],
A[0][1][0][0],A[0][1][0][1],A[0][1][1][0],A[0][1][1][1],
A[1][0][0][0],A[1][0][0][1],A[1][0][1][0],A[1][0][1][1],
A[1][1][0][0],A[1][1][0][1],A[1][1][1][0],A[1][1][1][1]。
(2)按列优先存储:
A[0][0][0][0],A[1][0][0][0],A[0][1][0][0],A[1][1][0][0],
A[0][0][1][0],A[1][0][1][0],A[0][1][1][0],A[1][1][1][0],
A[0][0][0][1],A[1][0][0][1],A[0][1][0][1],A[1][1][0][1],
A[0][0][1][1],A[1][0][1][1],A[0][1][1][1],A[1][1][1][1]。
链接
void EMove_k(int A[n], int k){//把数组A的元素循环右移k位,只用一个辅助空间
for(int i=1;i<=k;i++)
if(n%i==0 && k%i==0)//求n和k的最大公约数p
p = i;
for(int i=0;i<p;i++){
j = i;
m = (i+k)%n;
temp = A[i];
while(m != i){//循环右移一步
A[j] = temp;
temp = A[m];
A[m] = A[j];
j = m;
m = (j+k)%n;
}
A[i] = temp;
}
}
(1)算法1:依题意,先求出每行的最小值元素,放入 min[m] 之中,再求出每列的最大值元素,放入 max[n] 之中,若某元素既在 min[i] 中,又在 max[j] 中,则该元素 A[i][j] 便是马鞍点,找出所有这样的元素,即找到了所有马鞍点。
void Get_Saddle1(int A[m][n]){
have = 0;
for(int i=0;i<m;i++){//计算每行的最小值元素,放入min[m]
min[i] = A[i][0];
for(int j=1;j<n;j++)
if(A[i][j] < min[i])
min[i] = A[i][j];
}
for(int j=0;j<n;j++){//计算每列的最大值元素,放入max[m]
max[j] = A[0][j];
for(int i=1;i<m;i++)
if(A[i][j] > max[j])
max[j] = A[i][j];
}
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
if(min[i] == max[j]){//判定是否为马鞍点
print(A[i][j]);//显示马鞍点
have = 1;
}
if(!have)
print("没有鞍点\n");
}
(2)算法2:依题意,先求出某行的最小值元素 min,再判断该 min 是否是其所在列的最大值元素,如果是,则该元素便是马鞍点,找出每一行中所有这样的元素,即找到了所有马鞍点。
void Get_Saddle2(int A[m][n]){
for(int i=0;i<m;i++){
for(min=A[i][0],j=0;j<n;j++)
if(A[i][j] < min)//求一行中的最小值
min = A[i][j];
for(j=0;j<n;j++)
if(A[i][j] == min){//判断该最小值是否为马鞍点
for(flag=1,k=0;k>m;k++)
if(min < A[k][j])
flag = 0;
if(flag)
print(A[i][j]);//显示马鞍点
}
}
}
//稀疏矩阵的三元组顺序表存储表示
#define MAXSIZE 12500 //假设非零元素个数的最大值为12500
typedef struct{
int i,j;//该非零元的行下标和列下标
ElemType v;//非零元值
}Teiple;
typedef struct{
Triple data[MAXSIZE + 1];//非零元三元组,data[0]未用
int mu, nu, tu;//矩阵的行数、列数和非零元个数
}TSMatrix;
void TSMatrix_Add(TSMatrix A, TSMatrix B, TSMatrix &C){//三元组顺序表表示的稀疏矩阵加法
C.mu = A.mu;
C.nu = A.nu;
C.tu = 0;
pa = 1;
pb = 1;
pc = 1;
for(row=1;row<=A.mu;row++){//对居中的每一行进行相加
while(A.data[pa].i < row && pa <= A.tu)
pa++;
while(B.data[pb].i < row && pb <= B.tu)
pb++;
while(A.data[pa].i == row && B.data[pb].i == row && pa <= A.tu && pb <= B.tu){//行列相等的元素
if(A.data[pa].j == B.data[pb].j){
ce = A.data[pa].e + B.data[pb].e;
if(ce){//和不为0
C.data[pc].i = row;
C.data[pc].j = A.data[pa].j;
C.data[pc].e = ce;
pa++;
pb++;
pc++;
}
}
else if(A.data[pa].j > B.data[pb].j){
C.data[pc].i = row;
C.data[pc].j = B.data[pb].j;
C.data[pc].e = B.data[pb].e;
pb++;
pc++;
}
else{
C.data[pc].i = row;
C.data[pc].j = A.data[pa].j;
C.data[pc].e = A.data[pa].e;
pa++;
pc++;
}
}
while(A.data[pa]==row && pa<=A.tu){//插入A中剩余的元素(第row行)
C.data[pc].i = row;
C.data[pc].j = A.data[pa].j;
C.data[pc].e = A.data[pa].e;
pa++;
pc++;
}
while(B.data[pb]==row && pb<=B.tu){//插入B中剩余的元素(第row行)
C.data[pc].i = row;
C.data[pc].j = B.data[pb].j;
C.data[pc].e = B.data[pb].e;
pb++;
pc++;
}
}
C.tu = pc;
}
串
排序
可以把排序算法的输出解释为对一个待排序的下标求一种排列,使得序列中的元素按照升序排序。例如,待排序序列是 (a1, a2, … , an),则输出是这些元素的一个排序。因此,对于一个任意的 n 个元素的序列排序后,可能的输出有 n! 个,即有 n! 个不同的比较路径。在排序过程中,每次比较会有两种情况出现,若整个排序需要进行 t 次比较,则会出现 2t 种情况,于是有:22 ≥ n!,即 t ≥ log2(n!)。当待排序元素个数 n 非常大时,有 t ≥ log2(n!) ≈ nlog2n。
(1)直接插入排序
初始关键字:[503] 017 512 061 908 170 897 275 653 426 154
i=2:(017)[017 503] 512 061 908 170 897 275 653 426 154
i=3:(512)[017 503 512] 061 908 170 897 275 653 426 154
i=4:(061)[017 061 503 512] 908 170 897 275 653 426 154
i=5:(908)[017 061 503 512 908] 170 897 275 653 426 154
i=6:(170)[017 061 170 503 512 908] 897 275 653 426 154
i=7:(897)[017 061 170 503 512 897 908] 275 653 426 154
i=8:(275)[017 061 170 275 503 512 897 908] 653 426 154
i=9:(653)[017 061 170 275 503 512 653 897 908] 426 154
i=10:(426)[017 061 170 275 426 503 512 653 897 908] 154
i=11:(154)[017 061 154 170 275 426 503 512 653 897 908]
(2)希尔排序(增量 d[1]=5 )
初始关键字:503 017 512 061 908 170 897 275 653 426 154
一趟排序结果:154 017 275 061 426 170 897 512 653 908 503
二趟排序结果:154 017 275 061 426 170 503 512 653 908 897
三趟排序结果:017 061 154 170 275 426 503 512 653 897 908
(3)快速排序
初始关键字: 503 017 512 061 908 170 897 275 653 426 154
一次划分结果: 154 017 426 061 275 170 [503] 897 653 908 512
分别快排结果: 061 017 [154] 426 275 170 [503] 897 653 908 512
[017] [061] [154] 426 275 170 [503] 897 653 908 512
[017] [061] [154] 170 275 [426] [503] 897 653 908 512
[017] [061] [154] [170] [275] [426] [503] 897 653 908 512
[017] [061] [154] [170] [275] [426] [503] 512 653 [897] [908]
排序结果:[017] [061] [154] [170] [275] [426] [503] [512] [653] [897] [908]
(4)堆排序
初始关键字:503 017 512 061 908 170 897 275 653 426 154
初始堆:908 653 897 503 426 170 512 275 061 017 154
输出堆顶 908:154 653 897 503 426 170 512 275 061 017 [908]
调整后的新堆:897 653 512 503 426 170 154 275 061 017 [908]
输出堆顶 897:017 653 512 503 426 170 154 275 061 [897 908]
调整后的新堆:653 503 512 275 426 170 154 017 061 [897 908]
输出堆顶 653:061 503 512 275 426 170 154 017 [653 897 908]
调整后的新堆:512 503 170 275 426 061 154 017 [653 897 908]
输出堆顶 512:017 503 170 275 426 061 154 [512 653 897 908]
调整后的新堆:503 426 170 275 017 061 154 [512 653 897 908]
输出堆顶 503:154 426 170 275 017 061 [503 512 653 897 908]
调整后的新堆:426 275 170 154 017 061 [503 512 653 897 908]
输出堆顶 426:061 275 170 154 017 [426 503 512 653 897 908]
调整后的新堆:275 154 170 061 017 [426 503 512 653 897 908]
输出堆顶 275:017 154 170 061 [275 426 503 512 653 897 908]
调整后的新堆:170 154 017 061 [275 426 503 512 653 897 908]
输出堆顶 170:061 154 017 [170 275 426 503 512 653 897 908]
调整后的新堆:154 061 017 [170 275 426 503 512 653 897 908]
输出堆顶 154:017 061 [154 170 275 426 503 512 653 897 908]
调整后的新堆:061 017 [154 170 275 426 503 512 653 897 908]
输出堆顶 061:017 [061 154 170 275 426 503 512 653 897 908]
排序结果:[017 061 154 170 275 426 503 512 653 897 908]
(5)归并排序
初始关键字:[503] [017] [512] [061] [908] [170] [897] [275] [653] [426] [154]
一趟归并之后:[017 503] [061 512] [170 908] [275 897] [426 653] [154]
二趟归并之后:[017 061 503 512] [170 275 897 908] [154 426 653]
三趟归并之后:[017 061 170 275 503 512 897 908] [154 426 653]
四趟归并之后:[017 061 154 170 275 426 503 512 653 897 908]
(6)基数排序
初始关键字: 503 017 512 061 908 170 897 275 653 426 154
第一趟分配:Q[0] Q[1] Q[2] Q[3] Q[4] Q[5] Q[6] Q[7] Q[8] Q[9]
第一趟分配:170 061 512 503 154 275 426 017 908
653 897
第一趟收集:170 061 512 503 653 154 275 426 017 897 908
第二趟分配:Q[0] Q[1] Q[2] Q[3] Q[4] Q[5] Q[6] Q[7] Q[8] Q[9]
第二趟分配:503 512 426 653 061 170 897
908 017 154 275
第二趟收集:503 908 512 017 426 653 154 061 170 275 897
第三趟分配:Q[0] Q[1] Q[2] Q[3] Q[4] Q[5] Q[6] Q[7] Q[8] Q[9]
017 154 275 426 503 653 897 908
061 170 512
第三趟收集:017 061 154 170 275 426 503 512 653 897 908
采用基数排序方法时间复杂度最佳。因为这里英文单词的长度相等,且英文单词是由 26 个字母组成的,满足进行基数排序的条件,同时,依题意,m<<n,基数排序的时间复杂性由 O(m(n+rm)) 变成 O(n),因此时间复杂性最佳。
(1)当每个记录本身的信息量很大时,应尽量减少记录的移动,直接插入、冒泡和简单选择排序的平均时间复杂度为 O(n2),但简单选择排序中记录移动的次数最少,所以采用简单选择排序为佳。
(2)在直接插入、冒泡和简单选择排序中,直接插入和冒泡排序是稳定的,且两者在关键码呈基本正序时都居于最好时间复 O(n),因此可从中任选一个方法。
(3)就平均时间性能而言,基于比较和移动的排序方法中快速排序最佳。
(4)快速排序在最坏情况的时间复杂度为 O(n2),而堆排序和二路归并排序最坏情况的时间复杂度为 O(nlog2n),其中堆排序不稳定,所以应选择二路归并排序。
(5)按照关键码的结构,采用基数排序为好。
思路:类似快排思路。
#define LIST_INIT_SIZE 100
#define LISTINCREMENT 10
typedef struct{
ElemType *elem;//存储空间基址
int length;//当前长度
int listsize;//当前分配的存储容量以一数据元素存储长度为单位
}SqList;
void partition(SqList &L){
int i = 1, j = L.length - 1;
while(i < j){
while(i<j && L.elem[i]%2!=0)
i++;
while(i<j && L.elem[j]%2==0)
j--;
if(i < j)
L.elem[i]<-->L.elem[j];
}
}
void Divide(int L[], int n){//将L中值为负的记录调到非负的记录之前
low = 0;
high = n - 1;
while(low < high){
while(low<high && L[high]>=0)//以0作为虚拟枢轴
high--;
L[low]<-->L[high];
while(low<high && L[low]<0)
low++;
L[low]<-->L[high];
}
}
设立三个指针 i、j、k,其中:j 表示当前元素,i 以前的元素全部为红色,k 以后的元素全部为蓝色。可以根据 j 的颜色,把其交换到序列的前部或后部。
typedef enum {RED, WHITE, BLUE} color;//三种颜色
void Flag_Arrange(color a[], int n){
//把由三种颜色组成的序列重排为按照红、白、蓝的顺序排列
i = 0;
j = 0;
k = n - 1;
while(j <= k){
switch(a[j]){
case RED:
a[i]<-->a[j];
i++;
j++;
break;
case WHITE:
j++;
break;
case BLUE:
a[j]<-->a[k];
k--;
}
}
}
查找
查找成功的平均查找长度:ASL = (1+22+34+48+55) / 10 = 74 / 20 = 3.7
查找失败时最多与关键字的比较次数为 5。
二叉排序树:
删除72:
typedef struct BiTNode {
TElemType data;
struct BiTNode *lchild, *rchild;//左右孩子指针
}BiTNode, *BiTree;
int last = 0, flag = 1;
int Is_BSTree(BiTree T){
//判断二叉树T是否二叉排序树,是则返回1,否则返回0
if(T->lchild && flag)
Is_BSTree(T->lchild);
if(T->data < last)//与其中序前驱相比较
flag = 0;
last = T->data;
if(T->rchild && flag)
Is_BSTree(T->rchild);
return flag;
}
typedef struct BiTNode {
TElemType data;
struct BiTNode *lchild, *rchild;//左右孩子指针
}BiTNode, *BiTree;
void Print_NLT(BiTree T, int x){//从大到小输出二叉排序树T中所有不小于x的元素
if(T->lchild)
Print_NLT(T->lchild);
if(T->data < x)//当遇到小于x的元素时立即结束运行
exit();
printf("%d", T->data);
if(T->rchild)
Print_NLT(T->rchild);//先有后左的中序遍历
}
树
二叉树的先根序列与等价树的先根序列相同,二叉树的中序序列对应着树的后根序列。先转化成二叉树,再通过二叉树转换成树。(左儿子右兄弟,逆用)
当 n=2 时,要使其成为最优二叉树,必须使两个结点都成为叶子结点。
设 n=k 时成立,则当 n=k+1 时,要使其成为最优,必须用 k 个结点的哈夫曼树与第 k+1 个结点组成一个新的最优二叉树,所以 n=k+1 时也成立。
A:1010 B:00 C:10000 D:1001 E:11 F:10001 G:01 H:1011
#define MAX_TREE_SIZE 100 //二叉树的最大结点数
typedef TElemType SqBiTree[MAX_TREE_SIZE];//1号单元存储根结点
void PreOrder_Sq(SqBiTree BT){//先序遍历二叉树BT
InitStack(S);
p = 1;//p指示当前结点的位置
while(p<=n && !StackEmpty(S)){
while(p <= n){
Visit(BT[p]);
Push(S, p);
p = 2 * p;
}
if(!StackEmpty(S)){//栈非空,遍历右子树
Pop(S, p);
p = p * 2 + 1;
}
}
}
void PreOrder_Sq_Recursion(SqBiTree BT, int t, int n){//先序遍历二叉树(递归)
if(t <= n){
Visit[BT[t]];
PreOrder_Sq_Recursion(BT, t*2, n);
PreOrder_Sq_Recursion(BT, t*2+1, n);
}
}
typedef struct BiTNode {
TElemType data;
struct BiTNode *lchild, *rchild;//左右孩子指针
}BiTNode, *BiTree;
int LeadCount_BiTree(BiTree T){//求二叉树中叶子结点的数目
if(!T)//空树,没有叶子
return 0;
else
if(!T->lchild && !T->rchild)//叶子结点
return 1;
else//左、右子树的叶子数相加
return LeadCount_BiTree(T->lchild) + LeadCount_BiTree(T->rchild);
}
typedef struct BiTNode {
TElemType data;
struct BiTNode *lchild, *rchild;//左右孩子指针
}BiTNode, *BiTree;
int NodeCount_BiTree(BiTree T){//求二叉树中所有结点的数目
if(!T)//空树,没有叶子
return 0;
else{
int num1 = NodeCount_BiTree(T->lchild);
int num2 = NodeCount_BiTree(T->rchild);
return num1 + num2 + 1;
}
}
typedef struct BiTNode {
TElemType data;
struct BiTNode *lchild, *rchild;//左右孩子指针
}BiTNode, *BiTree;
int BranchCount_BiTree(BiTree T){//求二叉树中所有单分支结点数目
if(!T)//空树,没有叶子
return 0;
else{
int num1 = NodeCount_BiTree(T->lchild);
int num2 = NodeCount_BiTree(T->rchild);
if((!T->lchild && T->rchild) || (T->lchild && !T->rchild))
return num1 + num2 + 1;
else
return num1 + num2;
}
}
typedef struct BiTNode {
TElemType data;
struct BiTNode *lchild, *rchild;//左右孩子指针
}BiTNode, *BiTree;
void Revolute_BiTree(BiTree T){//交换所有结点的左右子树
Swap(T->lchild, T->rchild);//交换左右子树
if(T->lchild)
Revolute_BiTree(T->lchild);//左子树交换自己的左右子树
if(T->rchild)
Revolute_BiTree(T->rchild);//右子树交换自己的左右子树
}
typedef struct BiTNode {
TElemType data;
struct BiTNode *lchild, *rchild;//左右孩子指针
}BiTNode, *BiTree;
int Get_Sub_Depth(BiTree T, int x){//求二叉树中以值为x的结点为根的子树深度
if(T->data == x){
printf(Get_Depth(T));//找到了值为x的结点,求其深度
return 0;
}
else{
if(T->lchild)//在左子树中继续寻找
Get_Sub_Depth(T->lchild, x);
if(T->rchild)//在右子树中继续寻找
Get_Sub_Depth(T->rchild, x);
}
}
int Get_Depth(BiTree T){//求子树深度的递归算法
if(!T)
return 0;
else{
m = Get_Depth(T->lchild);
n = Get_Depth(T->rchild);
return (m>n?m:n) + 1;
}
}
typedef struct BiTNode {
TElemType data;
struct BiTNode *lchild, *rchild;//左右孩子指针
}BiTNode, *BiTree;
void NR_PreOrder(BiTree bt){//非递归先序遍历二叉树
InitStack(S);
if(!bt)
return;
p = bt;
while(p || !StackEmpty(S)){
while(p){//到达最左边的结点
Visit(p->data);//访问结点的数据域
Push(S, p);//将当前指针p压栈
p = p->lchild;//指针指向p的左孩子
}
if(!StackEmpty(S)){
Pop(S, p);//从栈中弹出栈顶元素
p = p->rchild;//指针指向p的右孩子结点
}
}
}
void CreateBiTree2(BiTree &T,char str[]){//建立二叉树链表(非递归)广义表
BiTree S[1000];//存放父节点
int flag = 0;//存入为左孩子还是右孩子,1为左孩子,2为右孩子
int k = 0;//记入前一个父节点的存放位置
BiTree p;
p = (BiTree)malloc(sizeof(BiTNode));//申请根节点的空间
p->data = str[0];
p->lchild = NULL;
p->rchild = NULL;
int i;
for(i=1;str[i] != '#';i++){
//printf("1\n");
if(str[i] == '('){//将父节点存放入数组中,并且下一个存放左孩子
flag = 1;
S[k++] = p;
}
else if(str[i] == ','){//下一个为存放右孩子
flag = 2;
}
else if(str[i] == ')'){//将一个父节点出数组
k--;
}
else{
p = (BiTree)malloc(sizeof(BiTNode));//申请一个孩子的空间
p->data = str[i];
p->lchild = NULL;
p->rchild = NULL;
if(flag == 1){//左孩子
S[k-1]->lchild = p;
}
else if(flag == 2){//右孩子
S[k-1]->rchild = p;
}
}
}
T = S[0];//将根返回
}
void Level_Order(BiTree BT){//按层次、非递归遍历二叉树
if(!BT)
return;
InitQueue(Q);
p = BT;//初始化
EnQueue(Q, p);//访问根结点,并将根结点入队
while(!QueueEmpty(Q)){//当队非空时重复执行下列操作
DeQueue(Q, p);//出队
Visit(p);
if(!p->lchild)
EnQueue(Q, p->lchild);//处理左孩子
if(!p->rchild)
EnQueue(Q, p->rchild);//处理右孩子
}
}
假设此二叉树总结点数为 n,度为 0 的结点数为 n0,度为 1 的结点数为 n1,度为 2 的结点数为 n2,则:n = n0 + n1 + n2(1)
在二叉树中,除了根结点外,其余结点都有一个分支进入,设 B 为分支总数,则:n = B + 1,由于这些分支是由度为 1 或度为 2 的结点射出的,所以有:B = n1 + 2n2,于是有:n = n1 + 2n2 + 1(2)
由(1)和(2)可得:n0 = n2 + 1
证毕。
假设此二叉树的深度为 k,根据二叉树性质 2 及完全二叉树的定义得到:2k-1-1<n≤2k-1,即 2k-1≤n<2k
于是 k-1≤log2n<k
即 k = ⌊log2n⌋ + 1。
(1)第 H 层上的结点数目为 kH-1。
(2)如果 p 是其双亲的最小的孩子(右孩子),则 p 减去根结点的一个结点,应是 k 的整数倍,该整数即为所在的组数,每一组为一棵满 k 叉树,正好应为双亲结点的编号。如果 p 是其双亲的最大的孩子(左孩子),则 p+k-1 为其最小的弟弟,再减去一个根结点,除以 k,即为其双亲结点的编号。
综合来说,对于 p 是左孩子的情况,i=(p+k-1)/k;对于 p 是右孩子的情况,i=(p-1)/k,如果左孩子的编号为 p,则其右孩子编号必为 p+k-1 ,所以,其双亲结点的编号为 i=⌊(p+k-2)/k⌋,向下取整。
(3)结点 p 的右孩子的编号为 kp+1,左孩子的编号为 kp+1-k+1=k(p-1)+2,第 i 个孩子的编号为 k(p-1)+2+i-1=kp-k+i+1。
(4)当(p-1)%k != 0时,结点 p 有右兄弟,其右兄弟的编号为 p+1。
根据树的定义,在一棵树中,除树根结点外,每个结点有且仅有一个前驱结点,也就是说,每个结点与指向它的一个分支一一对应,所以除树根结点之外的结点树等于所有结点的分支数,即度数,从而可得树中的结点数等于所有结点的度数加 1。总结点数为:1+n1+2n2+3n3+…+knk
度为 0 的结点数应为总结点数减去度不为 0 的结点数的总和,即
n0 = 1+n1+2n2+3n3+…+knk-(n1+n2+n3+…+nk)
n
0
=
1
+
∑
i
=
1
k
(
i
−
1
)
n
i
n_{0}=1+\sum ^{k}_{i=1}\left( i-1\right) n_{i}
n0=1+i=1∑k(i−1)ni
图
(1)
ID(1)=2, OD(1)=0; ID(2)=1, OD(2)=3; ID(3)=2, OD(3)=0;
ID(4)=1, OD(4)=2; ID(5)=0, OD(5)=2; ID(6)=2, OD(6)=1;
(2)
(3)
(4)
(5)
五个强连通分量:V1、V2、V3、V4、V5
数学归纳法
当 n=2 时,图G连通只需一条边,命题成立。
设 n=k(k>2) 时命题成立,即当 n=k 时,图G至少有 k-1 条边。
当 n=k+1(k2) 时,由上述假设可知,图G中已有 k 个顶点是连通的,且至少有 k-1 条边。现增加一个顶点 p 后,若 p 与图中已有的 k 个顶点没有边,显然 p 与图 G 不连通。若要求 k+1 个顶点组成的图是连通图,p 必须与图 G 原有的 k 个顶点中的任何一个顶点有边相连,即 k 条边就可使图连通。因此,当 n=k+1 时,图 G 至少有 k 条边,命题成立。证毕。
普里姆(Prim)算法
克鲁斯卡尔(Kruskal)算法
(1)
(2)
关键活动:<1, 3>、❤️, 2>、<2, 5>、<5, 6>,这些关键活动中任何一个提前完成,整个工程就能提前完成。
关键路径只有一条:(α,G,H,K,J,E,ω)