11:36:53 2019-08-30
学习
09:42:25 2019-08-31
补完昨天未写完的并查集优化
带权路径长度(WPL) (Weighted Path Length of Tree),设二叉树有n个叶子节点,每个叶子节点带有权值${w_k}$,从根节点到每个叶子节点的长度为${l_k}$,则每个叶子节点的带权路径长度之和就是:${WPL}=displaystyle sum_{k=1}^n{w_k}{l_k}$
最优二叉树或哈夫曼树:WPL最小的二叉树
哈夫曼树的构造:每次把权值最小的两课二叉树合并
利用最小堆 实现哈夫曼树:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<stdio.h> 2 #include<malloc.h> 3 4 typedef struct TreeNode* HuffmanTree; 5 struct TreeNode 6 { 7 int Weight; //权重 8 HuffmanTree Right, Left; 9 }; 10 typedef struct HeapStruct* MinHeap; 11 struct HeapStruct 12 { 13 HuffmanTree* Elements; 14 int Capacity; 15 int Size; 16 }; 17 18 MinHeap Create(int MaxSize); //创造一个空的最小堆 19 int IsFull(MinHeap H); 20 void Insert(MinHeap H, HuffmanTree T); 21 HuffmanTree DeleteMin(MinHeap H); //删除一个权重最小的节点 并返回 22 23 MinHeap Create(int MaxSize) 24 { 25 MinHeap H = (MinHeap)malloc(sizeof(struct HeapStruct)); 26 H->Elements = (HuffmanTree*)malloc(sizeof(struct TreeNode) * (MaxSize + 1)); 27 H->Capacity = MaxSize; 28 H->Size = 0; 29 H->Elements[0]->Weight = -1; //设置哨兵 30 return H; 31 } 32 int IsFull(MinHeap H) 33 { 34 return (H->Size == H->Capacity) ? 1 : 0; 35 } 36 void Insert(MinHeap H, HuffmanTree T) 37 { 38 if (IsFull(H)) 39 return; 40 int i = ++H->Size; 41 for (; T->Weight < H->Elements[i / 2]->Weight; i /= 2) 42 H->Elements[i] = H->Elements[i / 2]; 43 H->Elements[i] = T; 44 } 45 HuffmanTree DeleteMin(MinHeap H) 46 { 47 if (IsFull(H)) 48 return; 49 HuffmanTree MinItem = H->Elements[1]; 50 HuffmanTree Tmp = H->Elements[H->Size--]; 51 int Parent, Child; //这俩个记录的是位置 52 for (Parent = 1; Parent * 2 <= H->Size; Parent = Child) 53 { 54 Child = Parent * 2; 55 if (Child != H->Size && H->Elements[Child]->Weight > H->Elements[Child + 1]->Weight) 56 Child++; 57 if (Tmp->Weight <= H->Elements[Child]->Weight)break; //注意这边 找到后就立即结束循环 这样才能添加到正确位置 58 else 59 H->Elements[Parent] = H->Elements[Child]; 60 } 61 H->Elements[Parent] = Tmp; 62 return MinItem; 63 } 64 65 //建立最小堆 66 //元素已经读入 67 void PercDown(MinHeap H, int i) 68 { 69 int Parent, Child; 70 HuffmanTree Tmp = H->Elements[i]; 71 for (Parent = i; Parent * 2 <= H->Size; Parent = Child) 72 { 73 Child = Parent * 2; 74 if ((Child != H->Size) && (H->Elements[Child]->Weight > H->Elements[Child + 1]->Weight)) 75 Child++; 76 if (Tmp->Weight <= H->Elements[Child]->Weight)break; 77 else 78 H->Elements[Parent] = H->Elements[Child]; 79 } 80 H->Elements[Parent] = Tmp; 81 } 82 void BuildMinHeap(MinHeap H) 83 { 84 for (int i = H->Size / 2; i > 0; i--) //从最后一个有子节点的父节点开始 85 PercDown(H, i); 86 } 87 88 //利用最小堆 构建哈夫曼树 89 //每次把权值最小的两颗二叉树合并 90 HuffmanTree Huffman(MinHeap H) 91 { //假设H->Size个权值已经存在H->Elements[]->Weight里 92 int i; 93 HuffmanTree T; 94 BuildMinHeap(H); //将H->Elements[]按照权重调整为最小堆 95 for (int i = 1; i < H->Size; i++) //H->Size个元素合并 总共需要H->Size-1次 96 { 97 T = (HuffmanTree)malloc(sizeof(struct TreeNode)); 98 T->Left = DeleteMin(H); //取出权重最小的元素 作为T的左节点 99 T->Right = DeleteMin(H); //取出权重最小的元素 作为T的右节点 100 T->Weight = T->Left->Weight + T->Right->Weight; //计算新的权重 101 Insert(H, T); //将新T插入最小堆中; 102 } 103 T = DeleteMin(H); 104 return T; 105 }
哈夫曼树的特点:
①没有度为1的节点
②n个叶子节点的哈夫曼树公有2n-1个节点
因为$n_0=n_2+1$
③哈夫曼树的任意非叶节点的左右子树交换后任是哈夫曼树
④对同一组权值 可能会存在不同构的哈夫曼树 但他们的WPL是一样的
PTA第12题 读入一串数 将其处理为 最小堆 并输出 从某个下标i到根节点的数据(因为最大堆是一个完全二叉树)
要注意数据个数 以及数据最小值 我最开始取 哨兵H[0]=-1 那么肯定负数 就无法正确处理
还有发现的问题是 用的方法不一样 生成的最小(大)堆结果不一样
建立最大(小)堆的方式有两种(就我所知):
① 通过插入操作 不断将元素插入 复杂度为 ${O(NLogN)}$
② 先将数据按顺序存入(满足完全二叉树的特征) 在调整各个节点的位置 复杂度为 ${O(LogN)}$
利用插入操作:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #define _CRT_SECURE_NO_WARNINGS 2 #include<stdio.h> 3 #include<malloc.h> 4 5 typedef struct HeapStruct* MinHeap; 6 struct HeapStruct 7 { 8 int* Elements; 9 int Size; 10 int Capacity; 11 }; 12 int IsFull(MinHeap H) 13 { 14 return (H->Capacity == H->Size) ? 1:0; 15 } 16 MinHeap Create(int MaxSize) 17 { 18 MinHeap H = (MinHeap)malloc(sizeof(struct HeapStruct)); 19 H->Elements = (int*)malloc(sizeof(int) * (MaxSize + 1)); 20 H->Capacity = MaxSize; 21 H->Size = 0; 22 H->Elements[0] = -10001; 23 return H; 24 } 25 void Print(MinHeap H, int num) 26 { 27 for (int i = num; i >= 1; i /= 2) 28 { 29 if (i > 1) 30 printf("%d ", H->Elements[i]); 31 else 32 printf("%d", H->Elements[i]); 33 } 34 printf(" "); 35 } 36 void Insert(MinHeap H, int Element) 37 { 38 if (IsFull(H)) 39 return; 40 int i = ++H->Size; 41 for (; Element < H->Elements[i / 2]; i /= 2) 42 H->Elements[i] = H->Elements[i / 2]; 43 H->Elements[i] = Element; 44 } 45 46 int main() 47 { 48 MinHeap H; 49 H = Create(1001); 50 int N, M; 51 scanf("%d %d ", &N, &M); 52 for (int i = 1; i < N + 1; i++) 53 { 54 int num; 55 scanf("%d", &num); 56 Insert(H, num); 57 } 58 for (int i = 0; i < M; i++) 59 { 60 int num; 61 scanf("%d", &num); 62 Print(H, num); 63 } 64 return 0; 65 }
利用读入 再 转换的操作
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #define _CRT_SECURE_NO_WARNINGS 2 #include<stdio.h> 3 #include<malloc.h> 4 5 typedef struct HeapStruct* MinHeap; 6 struct HeapStruct 7 { 8 int* Elements; 9 int Size; 10 int Capacity; 11 }; 12 13 MinHeap Create(int MaxSize) 14 { 15 MinHeap H = (MinHeap)malloc(sizeof(struct HeapStruct)); 16 H->Elements =(int*)malloc(sizeof(int) * (MaxSize + 1)); 17 H->Capacity = MaxSize; 18 H->Size = 0; 19 H->Elements[0]= -10001; 20 return H; 21 } 22 //建立最小堆 23 void PrecDown(MinHeap H, int i) //下滤 24 { 25 int Parent, Child; 26 int Tmp = H->Elements[i]; 27 for (Parent = i; Parent * 2 <= H->Size; Parent = Child) 28 { 29 Child = Parent * 2; 30 if ((Child != H->Size) && (H->Elements[Child]>H->Elements[Child + 1])) 31 Child++; 32 if (Tmp <= H->Elements[Child])break; 33 else 34 H->Elements[Parent] = H->Elements[Child]; 35 } 36 37 H->Elements[Parent] = Tmp; 38 } 39 void BuildMinHeap(MinHeap H) 40 { 41 int i; 42 for (i = H->Size / 2; i > 0; i--) 43 PrecDown(H, i); 44 } 45 void Print(MinHeap H, int num) 46 { 47 for (int i = num; i >= 1; i /= 2) 48 { 49 if(i>1) 50 printf("%d ", H->Elements[i]); 51 else 52 printf("%d", H->Elements[i]); 53 } 54 printf(" "); 55 } 56 int main() 57 { 58 MinHeap H; 59 H = Create(1001); 60 int N, M; 61 scanf("%d %d ", &N, &M); 62 for (int i = 1; i < N+1; i++) 63 { 64 int num; 65 scanf("%d", &num); 66 H->Elements[++H->Size] = num; 67 } 68 BuildMinHeap(H); 69 for (int i = 0; i < M; i++) 70 { 71 int num; 72 scanf("%d", &num); 73 Print(H, num); 74 } 75 return 0; 76 }
并查集 简单版本
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<stdio.h> 2 3 //利用树结构表示集合,树的每个节点代表一个集合元素 4 //利用双亲表示法:孩子指向双亲 5 6 //利用数组实现集合及运算 7 typedef struct 8 { 9 int Data; 10 int Parent; 11 }SetType; 12 13 //查找某个元素所在的集合 (用根节点表示) 返回节点下标 14 int Find(SetType S[], int Element) 15 { 16 int i; 17 for (i = 0; i<MaxSize&&S[i].Data != Element; i++); //MaxSize是集合最大最大个数 18 if (i >= MaxSize)return -1; //未找到Element ,返回-1 19 for (; S[i].Parent >= 0;i = S[i].Parent); 20 return i; 21 } 22 23 void Union(SetType S[], int X1, int X2) 24 { 25 int Root1; 26 int Root2; 27 Root1 = Find(S, X1); 28 Root2 = Find(S, X2); 29 if (Root1 != Root2) S[Root2].Parent = Root1; 30 } 31 32 33 //集合的简化表示 34 //任何有限集合的(N个)元素都可以被一一映射为整数 0 到 N-1 35 //利用数组下标存 元素 数组中的元素 为父节点 36 37 typedef int ElementType; //默认元素用非负整数表示 38 typedef int SetName; //默认用根节点下标作为集合名称 39 typedef ElementType SetType[MaxSize]; 40 41 SetName Find(SetType S, ElementType X) 42 {/*默认集合元素全部初始化为-1*/ 43 for (; S[X] >= 0; X = S[X]); 44 return X; 45 } 46 void Union(SetType S, SetName Root1, SetName Root2) 47 {/*默认Root1和Root2是不同集合的根节点*/ 48 S[Root2] = Root1; 49 }
PTA第13题
并查集算法的实践 听了一点课后就做 答案部分正确 我的思路比较简单
课上讲利用数组来 简化表示集合 因为$N$个元素可以一一映射到${0}sim{N-1}$
每次读入俩个数据 若是连接 就把第一个元素的父节点设置为第二个元素
若是判断是否连接 就插叙两个元素的父节点是否相等
这样做出来答案是部分正确 没有按秩Union操作 没有压缩路径操作
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #define _CRT_SECURE_NO_WARNINGS 2 #include<stdio.h> 3 4 typedef int ElementType; 5 typedef ElementType SetName; 6 typedef ElementType SetType[10001]; 7 8 SetName Find(SetType S, ElementType X) 9 { 10 for (; S[X] >= 0; X = S[X]); 11 return X; 12 } 13 14 void Union(SetType S, ElementType X1, ElementType X2) 15 { 16 SetName Root1 = Find(S, X1); 17 SetName Root2 = Find(S, X2); 18 if (Root1 != Root2) S[Root2] = Root1; 19 } 20 21 int main() 22 { 23 SetType S; 24 for (int i = 0; i < 20; i++) S[i] = -1; 25 int N; 26 scanf("%d ", &N); 27 char Cand; 28 while (scanf("%c",&Cand)&&Cand!='S') 29 { 30 int Com1, Com2; 31 scanf("%d%d ", &Com1, &Com2); 32 if (Cand == 'C') 33 { 34 if (Find(S, Com1) == Find(S, Com2)) 35 printf("yes "); 36 else 37 printf("no "); 38 } 39 else if (Cand == 'I') 40 { 41 S[Com1] = Com2; 42 } 43 } 44 int Size = 0; 45 for (int i = 0; i < N; i++) 46 { 47 if (S[i] == -1) 48 Size++; 49 } 50 if (Size == 1) 51 printf("The network is connected."); 52 else 53 printf("There are %d components.", Size); 54 return 0; 55 }
学到了陈越姥姥的新词 TSSN too simple sometimes naive
将 Union改为按秩 归并结果就正确了
如果还要优化 就得使用路径压缩
优化并查集的操作 :按秩归并 路径压缩
改进后的解题
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #define _CRT_SECURE_NO_WARNINGS 2 #include<stdio.h> 3 4 typedef int ElementType; 5 typedef ElementType SetName; 6 typedef ElementType SetType[10001]; 7 8 SetName Find(SetType S, ElementType X) 9 { 10 /*for (; S[X] >= 0; X = S[X]); 最开始的做法 11 return X;*/ 12 13 //路径压缩 14 if (S[X]< 0) 15 return X; 16 else 17 return S[X] = Find(S, S[X]); 18 } 19 20 void Union(SetType S, ElementType X1, ElementType X2) //不管是按高度还是按规模 两者统称按秩归并 21 { //最坏情况下的树高是O(LogN) 22 SetName Root1 = Find(S, X1); 23 SetName Root2 = Find(S, X2); 24 //if (Root1 != Root2) S[Root2] = Root1; //最开始TSSN的做法 25 26 /*if (Root1 == Root2)return; 27 if (S[Root1] < S[Root2]) //利用规模来计算 28 { 29 S[Root1] += S[Root2]; 30 S[Root2] = Root1; 31 } 32 else 33 { 34 S[Root2] += S[Root1]; 35 S[Root1] = Root2; 36 }*/ 37 38 if (S[Root1] < S[Root2]) //这里用的是树高 39 S[Root2] = Root1; 40 else if(S[Root2]<S[Root1]) 41 S[Root1] = Root2; 42 else //树高相等 43 { 44 S[Root1]--; 45 S[Root2] = Root1; 46 } 47 } 48 49 /*int main() //我的写法 50 { 51 SetType S; 52 int N; 53 scanf("%d ", &N); 54 for (int i = 0; i <N+1; i++) S[i] = -1; 55 char Cand; 56 while (scanf("%c",&Cand)&&Cand!='S') 57 { 58 int Com1, Com2; 59 scanf("%d%d ", &Com1, &Com2); 60 if (Cand == 'C') 61 { 62 if (Find(S, Com1) == Find(S, Com2)) 63 printf("yes "); 64 else 65 printf("no "); 66 } 67 else if (Cand == 'I') 68 Union(S, Com1, Com2); 69 } 70 int Size = 0; 71 for (int i =1; i < N+1; i++) 72 { 73 if (S[i]<0) 74 Size++; 75 } 76 if (Size == 1) 77 printf("The network is connected."); 78 else 79 printf("There are %d components.", Size); 80 return 0; 81 }*/ 82 83 void Input_connection(SetType S) 84 { 85 ElementType u, v; 86 scanf("%d %d ", &u, &v); 87 Union(S,u - 1, v - 1); 88 } 89 void Check_connection(SetType S) 90 { 91 ElementType u, v; 92 scanf("%d %d ", &u, &v); 93 if (Find(S, u - 1) == Find(S, v - 1)) 94 printf("yes "); 95 else printf("no "); 96 } 97 void Check_network(SetType S,int n) 98 { 99 int Size = 0; 100 for (int i = 0; i < n; i++) 101 { 102 if (S[i]<0) 103 Size++; 104 } 105 if (Size == 1) 106 printf("The network is connected. "); 107 else 108 printf("There are %d components. ", Size); 109 } 110 void Initialization(SetType S, int n) 111 { 112 for (int i = 0; i < n; i++) 113 S[i] = -1; 114 } 115 int main() 116 { 117 SetType S; 118 int n; 119 char in; 120 scanf("%d", &n); 121 Initialization(S, n); 122 do 123 { 124 scanf("%c", &in); 125 switch (in) 126 { 127 case 'I':Input_connection(S); break; 128 case 'C':Check_connection(S); break; 129 case 'S':Check_network(S,n); break; 130 } 131 }while (in != 'S'); 132 return 0; 133 }
还有学习到的是 尾递归在程序运行过程中 会被计算机转化为循环 所以不会把内存爆掉