首先是两种排序方法,归并排序和快速排序。
归并排序的思想就是分治,分而治之,分的策略是:将一个数组从中间切开,左右两部分继续对半分,直到分到只包含一个元素即可。
合的策略是:将两个各自排好序的数组合并为一个新的排好序的数组。为什么说两个数组是各自排好序的呢?从最小的单元--一个元素看起,显然是有序的。只要从一到二的过程保证有序,那么从二到四.....自然都是有序数组之间的合并。
代码如下:
#include "iostream" using namespace std; //[start, mid]是第一个区间; //[start+1, end]是第二个区间 void *MergeGroups(int *a, int start, int mid, int end) { int *temp = new int[end - start + 1]; int i = start; int j = mid + 1; int k = 0; while (i <= mid && j <= end) { if (a[i] <= a[j]) { temp[k++] = a[i]; i++; } else { temp[k++] = a[j]; j++; } } while (i <= mid) { temp[k++] = a[i]; i++; } while (j <= end) { temp[k++] = a[j]; j++; } for (int i = 0; i < k; i++) { a[start + i] = temp[i]; } delete[] temp; } //start 和 end均指下标,长度为n的数组,end为n-1 void MergeSort(int *a, int start, int end) { if (a == NULL || start >= end) { return; } int mid = (start + end) / 2; MergeSort(a, start, mid); MergeSort(a, mid + 1, end); MergeGroups(a, start, mid, end); } int main(){ int a[] = {80,30,60,40,20,10,50,70}; int ilen = (sizeof(a)) / (sizeof(a[0])); MergeSort(a,0,ilen-1); for(int i=0;i<ilen;i++){ cout<<a[i]<<endl; } }
重点在合并这一部分,要考虑一种特殊情况,最后数组a所有的元素全被加到了结果数组上,数组b还剩下一截,这种情况怎么办?
第二个算法是快速排序。
在我看来,快速排序的思想就是冒泡排序加上归并排序。
为什么这么说呢?在选取基准点,调整基准点的位置,使其前面的数字全小于基准值,后面的数字全大于基准值。这是和冒泡排序的思想相似的,通过比较交换数字的位置,不过它的想法比较巧妙,从两个方向遍历,最终到达的效果是基准值在数组中间。
第二点,这里面又有分治的思想,就是基准点左右两部分各自看作一个新的数组继续执行前面的操作(找基准点,调整基准点的位置)
代码如下:
#include "iostream" using namespace std; //快速排序遍历数组,有两个方向,从左向右遍历,执行++ //操作,找比基准值大的值;从右向左遍历,执行--操作, //找比基准值小的值。默认基准值为第一个数。 void QuickSort(int *a, int start, int end) { if (start >= end || a == NULL) { return; } int i = start; int j = end; int level = a[i]; bool right = true; while (i < j) { if (right) { if (a[j] < level) { int temp = a[j]; a[j] = a[i]; a[i] = temp; right = false; } else { j--; } } else { if (a[i] > level) { int temp = a[i]; a[i] = a[j]; a[j] = temp; right = true; } else { i++; } } // cout<<"level value is: "<<level<<endl; // for (int i = 0; i < 8; i++) // { // cout << a[i] <<" "; // } // cout<<endl; } QuickSort(a, start, i); QuickSort(a, i + 1, end); } int main() { int a[] = {80, 30, 60, 40, 20, 10, 50, 70}; int ilen = (sizeof(a)) / (sizeof(a[0])); cout << ilen << endl; QuickSort(a, 0, ilen - 1); for (int i = 0; i < ilen; i++) { cout << a[i] << endl; } }
这里面重点是有一个bool值right来控制当前遍历的方向,right为true,代表自右向左,反之,为自左向右。第二个重点是QuickSort()代码中,一定需要判断start是否大于等于end,如果不判断的话,直接执行下面的代码,会导致内存溢出的!
接下来就是最小生成树的两个算法,kruskal算法和prim算法。
先介绍prim算法吧。prim算法是这样想的。一个图想找出其中的最小生成树。最小生成树起码是一个连通图。那么我们从一个点入手,除了这个点是我们已知的外,其他点都是一片黑暗,那么我们怎么使这个点和其他的点连通呢?我们知道从这个点到其他点有几条边。我们可以通过这几条边来联系其他的顶点。但我们的要求又是最小生成树。所有我们采用这些边里面最小的那条边。通过这个步骤,我们成功的连通了一个点。那么接下来,还是一样的想法,这两个点延伸出边连接未知的点。我们根据最小生成树的原则,继续选取最小值的边,如此往复,直到没有什么需要连通的点,我们选取的边就是最小生成树的边集。
代码如下:
#include "iostream" using namespace std; const int maxN = 20; const int INF = 1 << 20; int N = 6,M; int edge[maxN][maxN]; int lowcost[maxN]; bool visited[maxN]; void init(){ edge[1][1]=INF; edge[1][2]=6; edge[1][3]=1; edge[1][4]=5; edge[1][5]=INF; edge[1][6]=INF; edge[2][1]=6; edge[2][2]=INF; edge[2][3]=5; edge[2][4]=INF; edge[2][5]=3; edge[2][6]=INF; edge[3][1]=1; edge[3][2]=5; edge[3][3]=INF; edge[3][4]=5; edge[3][5]=6; edge[3][6]=4; edge[4][1]=5; edge[4][2]=INF; edge[4][3]=5; edge[4][4]=INF; edge[4][5]=INF; edge[4][6]=2; edge[5][1]=INF; edge[5][2]=3; edge[5][3]=6; edge[5][4]=INF; edge[5][5]=INF; edge[5][6]=6; edge[6][1]=INF; edge[6][2]=INF; edge[6][3]=4; edge[6][4]=2; edge[6][5]=6; edge[6][6]=INF; } void prim(){ int first = 1; for(int i=1;i<=N;i++){ lowcost[i] = edge[first][i]; visited[i] = false; } visited[1] = true; int sum = 0; for(int i=1;i<N;i++){ int min = INF; int k = 0; for(int j=1;j<=N;j++){ if(!visited[j]&&lowcost[j]<min){ min = lowcost[j]; k = j; } } visited[k] = true; sum += min; for(int j=1;j<=N;j++){ if(!visited[j]&&lowcost[j]>edge[k][j]){ lowcost[j] = edge[k][j]; } } } cout<<sum<<endl; } int main(){ init(); prim(); }
这里面涉及到了一个图使用邻接矩阵来表示的知识点。
第二个最小生成树的算法就是kruskal算法。这个算法使用到了一个性质,n个顶点的最小生成树有n-1条边。结合最小生成树的原则,那么我们将图中所有的边按权值从大到小排列,取前面的n-1条边即可。这就是kruskal的思想,但是还要解决一个问题,万一前面的边形成环怎么办?我们知道树是无圈图,那么取前n-1条边就不是一个正确的选择了。那么该怎么做呢?很简单我们在从边的有序列表中遍历的时候,每取一条边,就需要判断加入这条边后会不会形成圈,如果会的话,则舍弃这条边。反之取用。这个方法就是并查集。这个数据结构有find和merge两个方法。我们只需要find方法即可。
代码如下:
#include "iostream" using namespace std; typedef struct Edge{ int start; int end; int weight; }; void Sort_Edge(Edge* e,int start,int end){ if(e == NULL || start >= end){ return; } int i = start; int j = end; int level = e[start].weight; bool right = true; while(i < j){ if(right){ if(e[j].weight < level){ Edge temp = e[i]; e[i] = e[j]; e[j] = temp; right = false; }else{ j--; } }else{ if(e[i].weight > level){ Edge temp = e[j]; e[j] = e[i]; e[i] = temp; right = true; }else{ i++; } } } Sort_Edge(e,start,i); Sort_Edge(e,i+1,end); } int parent[100]; //此处x为下标。 int Find(int* parent,int x){ while(parent[x]>=0){ x = parent[x]; } return x; } void kruskal(Edge* e,int p_num,int e_num){ Sort_Edge(e,0,e_num-1); int en = 0; for(int i = 0;i<e_num;i++){ int start_find = Find(parent,e[i].start); int end_find = Find(parent,e[i].end); if(start_find != end_find){ en++; cout<<e[i].start<<" "<<e[i].end<<" "<<e[i].weight<<endl; if(start_find > end_find){ parent[start_find] = end_find; }else{ parent[end_find] = start_find; } } if(en>=p_num-1){ break; } } } int main(){ for(int i=0;i<100;i++){ parent[i] = -1; } cout<<"请输入顶点的数量和边的数量"<<endl; int p_num,e_num; cin>>p_num>>e_num; cout<<"请输入边,一条边一行,格式为start end weight"<<endl; Edge* e = new Edge[e_num]; for(int i=0;i<e_num;i++){ cin>>e[i].start>>e[i].end>>e[i].weight; } kruskal(e,p_num,e_num); }
这里面表示一个图用的是边的数组。
最后一种算法是求从一定点到图其他点的最短路径。也就是dijkstra算法。
这个算法的思想和prim的想法是一样的。我们从一定点开始,通过从这一定点发射出的几条边,我们可以知道这一点到这几条边连通的点的最短距离(此问题的前提是非负边,所以直达的边就是最短路径。)其他的点,我们暂且不清楚,可以记作无穷大,而我们继续寻找最小边,每寻到一个点,这个点又存在几条向外发射的边。连接其他的点。我们可以使用这条边来更新最短路径的列表。如此,知道所有的点都加入了集合。
代码如下:
#include "iostream" using namespace std; const int maxN = 20; const int INF = 1 << 20; int N = 6, M; int edge[maxN][maxN]; int lowcost[maxN]; bool visited[maxN]; void init() { edge[1][1] = INF; edge[1][2] = 6; edge[1][3] = 1; edge[1][4] = 5; edge[1][5] = INF; edge[1][6] = INF; edge[2][1] = 6; edge[2][2] = INF; edge[2][3] = 5; edge[2][4] = INF; edge[2][5] = 3; edge[2][6] = INF; edge[3][1] = 1; edge[3][2] = 5; edge[3][3] = INF; edge[3][4] = 5; edge[3][5] = 6; edge[3][6] = 4; edge[4][1] = 5; edge[4][2] = INF; edge[4][3] = 5; edge[4][4] = INF; edge[4][5] = INF; edge[4][6] = 2; edge[5][1] = INF; edge[5][2] = 3; edge[5][3] = 6; edge[5][4] = INF; edge[5][5] = INF; edge[5][6] = 6; edge[6][1] = INF; edge[6][2] = INF; edge[6][3] = 4; edge[6][4] = 2; edge[6][5] = 6; edge[6][6] = INF; } void prim() { int first = 1; for (int i = 1; i <= N; i++) { lowcost[i] = edge[first][i]; visited[i] = false; } visited[1] = true; int sum = 0; for (int i = 1; i < N; i++) { int min = INF; int k = 0; //此处有异议,j可否从i开始? //不可以,此处循环是在寻找最小临界边,我们不能保证j之前的点都在新集合中。 for (int j = 1; j <= N; j++) { if (!visited[j] && lowcost[j] < min) { min = lowcost[j]; k = j; } } visited[k] = true; for (int j = 1; j <= N; j++) { if (!visited[j] && lowcost[k] + edge[k][j] < lowcost[j]) { lowcost[j] = lowcost[k] + edge[k][j]; } } } for(int i=1;i<=N;i++){ cout<<lowcost[i]<<" "; } cout<<endl; } int main() { init(); prim(); }
这里面只需要将prim的代码,中更新lowcost[j]的那个For循环改一下即可。