第八章 排序
8.3交换排序
2.编写双向冒泡排序算法,在正反两个方向交替进行扫描,即第一趟把关键字最大的元素放在序列的最后面,第二趟把关键字最小的元素放在最前面,如此反复进行。
void BubbleSort(ElemType A[],int n){ int low = 0; int high = n-1; bool flag = true; while(low<high && flag){ flag = false; for(i=low;i<high;i++){ if(A[i]>A[i+1]){ swap(A[i],A[i+1]); flag = true; } } high--; for(i=high;i>low;i--){ if(A[i]>A[i-1]){ swap(A[i],A[i-1]); flag = true; } } low++; } } |
3.已知线性表按顺序存储,且每个元素都是不相同的整数型元素,设计把所有奇数移动到所有偶数前的算法(要求时间最少,辅助空间最少)。
// 思路:采用快速排序的划分思想。先从前往后找到一个偶数元素L(i),再从后往前找到一个奇数元素L(j),将二者交换,直至i大于j。
void move(ElemType A[],int len){ // 1 2 5 8 1 2 5 8 11 int low=0,high=len-1; while(low<high){ while(low<high && A[low]%2!=0) // 找一个偶数 low++; while(low<high && A[high]%2!=1) // 找一个奇数 high--; if(low<high) swap(A[low],A[high]); low++; high--; } } |
4.重新编写快速排序的划分算法,使得每次选取的的枢轴值都是从当前子表中随机选择的。
void Partition2(ElemType A[],int low,int high){ int rand_index = low + rand()%(high-low+1); swap(A[rand_index],A[low]); ElemType pivot = A[low]; // 选取枢轴 int i = low; // low X ··· high for(int j=low+1;j<=high;j++) // i j if(A[j]<pivot) swap(A[++i],A[j]); swap(A[i],A[low]); // 最终使得A[low ··· i]中的所有元素都小于pivot return i; // 返回基准元素位置 }// 核心思想:j从子表的第二个元素开始遍历,如果这个元素小于枢轴,就让它交换到前面来(可以认为从第二个元素开始,初始是一个空表) // 5 || 1 7 11 4 9 8 // 5 || 1 4 11 7 9 8 // i // 4 1 5 11 7 9 8 |
5.试编写一个算法,使之能够在数组L[1...n]中找出第k小的元素(即从小到大排序后处于第k个位置的元素)。
// 思路:最直接的办法是直接进行排序(快排、堆排序(小根堆)),O(nlog2n) O(n+klog2n)。 // 巧妙办法:选取枢轴,进行类似于快速排序的划分操作。L[1...n]被划分成L[1...m-1]和L[m+1...n] L(m)=pivot // 相当于划分后,确定了所选择枢轴的最终位置。 // 1) m=k 直接返回pivot // 2) m<k 所找元素落在右子表,对L[m+1,n]递归查找第k-m小的元素。(左子表中的m-1个元素和枢轴都小于目标元素) // 3) m>k 所找元素落在左子表,对L[1,m-1]递归查找第k小元素 // 时间复杂度O(n) 空间复杂度取决于具体情况 void Find_KMin(ElemType A[],int low,int high,int k){ int pivot = A[low]; int low_temp = low; // 暂存,后续递归时要使用 int high_temp = high; while(low<high){ while(low<high && A[high]>=pivot) --high; A[low] = A[high]; while(low<high && A[low]<=pivot) ++low; A[high] = A[low]; } A[low] = pivot; // 枢轴元素就位 if(low==k) return A[low]; else if(low<k) Find_KMin(A,low+1,high_temp,k-low); // 在右子表中找第k-low小的元素 else Find_KMin(A,low_temp,low-1,k); // 在左子表中找第k小的元素 } |
6.荷兰国旗问题:设有一个仅由红、白、蓝三种颜色条块组成的条块序列,请编写一个时间复杂度为O(n)的算法,使得这些条块按照红白蓝的顺序排好,即排列成荷兰国旗的图案。
// 思路:将红色条块交换到线性表的最前面,蓝色条块交换到线性表的最后面。 // 使用三个游标,i,r,b i遍历数组,r之前的条块为红色,b之后的条块都为蓝色 r初始为0,b初始为n-1 typedef enum{RED,WHITE,BLUE} color; // 设置枚举数组 void Flag_Arrange(color a[],int n){ int i = 0; int r = 0; int b = n-1; while(i<=b){ switch(A[i]){ case RED: // 红 白 红 白 Swap(A[i],A[r]); // r i i++; r++; break; case WHITE: i++; break; case BLUE: Swap(A[i],A[b]); // 白 蓝 红 白 蓝 b--; // i b break; // 此处i不能++,以防出现交换后A[i]仍未蓝色的情况 } } } // 思考:为什么红色不需要考虑交换后仍然为红色的问题? // 因为,i是从0开始遍历的,如果扫描到红色,会自己和自己进行交换,r会++,r所指的位置不可能是红色。参见上方示例。而b所指的位置并没有被遍历处理过。 |
7.[2016]已知由n(n>=2)个正整数构成的集合A={ak|0<=k<n},将其划分为两个不相交的子集A1和A2,元素个数分别是n1和n2,A1和A2中的元素之和分别为S1和S2。设计一个尽可能高效的划分算法,满足|n1-n2|最小且|S1-S2|最大。
// 思路:将最小的 n/2(向下取整)个元素放在A1中,其余元素放在A2中。 无需对所有元素进行全部排序。 // 仿照快速排序,基于枢轴划分成两个子集。 // 1) i=n/2(向下取整) 分组完成 // 2) i<n/2(向下取整) 枢轴及枢轴之前的元素均属于A1,对枢轴之后进行再次划分 // 3) i>n/2(向下取整) 枢轴及枢轴之后的元素均属于A2,对枢轴之前进行再次划分 void SetPartition(int A[],int n){ int pivot; int low = 0, high = n-1; int low0 = 0, high0 = n-1; int k = n/2; bool flag = true; while(flag){ pivot = A[low]; while(low<high){ while(low<high && A[high]>=pivot) --high; if(low!=high) // 此处判断多余??? A[low] = A[high]; while(low<high && A[low]<=pivot) ++low; if(low!=high) A[high] = A[low]; } A[low] = pivot; if(low==k-1) // 枢轴是第n/2小元素,划分成功。(数组下标相差1) flag = false; else if(low<k-1){ // 对枢轴之后进行再次划分 0 1 ··· low ········· n-1 high = high0; low0 = ++low; // 暂存,以便后续使用 }else{ // 对枢轴之前进行再次划分 0 1 ········· low ··· n-1 low = low0; high0 = --high; // 暂存,以便后续使用 } } } |
8.4 选择排序
4.编写一个算法,在基于单链表表示的待排序关键字序列上进行简单选择排序。
// 思路:不断摘下待排序序列中关键字最大的结点,将其插入链表的最前端。 void SelectSort(LinkList &L){ // 不带头结点 LinkNode *head = L; // 待排序链表的头结点 L = NULL; LNode *max; // 暂存最大结点 LNode *maxpre; // 暂存最大结点的前驱 LNode *r; // 游标 LNode *pre; // 游标前驱 while(head!=NULL){ r = head; pre = NULL; max = head; // 假设待排序链表的头结点是最大的 maxpre = NULL; while(r!=NULL){ if(r->data > max->data){ max = r; maxpre = pre; } pre = r; // 游标、游标前驱后移 r = r->next; } if(max==head) // 最大结点是头结点 --> 头结点摘走,头指针要后移,不然断链了 head = head->next; else maxpre->next = max->next; max->next = L; // 前插至结果链表 L = max; // 结果链表头指针移动 } } |
5.设计一个算法,判断一个数据序列是否构成一个小根堆。
// 思路:将顺序表看作是一个完全二叉树,扫描所有分支结点。若孩子结点的关键字小于根结点的关键字,返回false。 bool IsMinHeap(ElemType A[],int len){ if(len%2==0){ // 存在一个单分支结点 if(A[len/2]>A[len]) // len/2 ---> 最后一个分支结点 (len/2)*2=len ----> 最后一个分支结点的左孩子 return false; for(i=len/2-1;i>=1;i--) // 判断所有双分支结点 if(A[i]>A[2*i] || A[i]>A[2*i+1]) // 如果根结点>左孩子 或者 根结点>右孩子 return false; }else{ // 没有单分支结点 for(i=len/2;i>=1;i--){ if(A[i]>A[2*i] || A[i]>A[2*i+1]) return false; } } } |
(三)8.6 各种内部排序算法的比较及应用
02.设顺序表用数组A[]表示,表中元素存储在数组下标1~m+n的范围内,前m个元素递增有序,后n个元素递增有序,设计一个算法,使得整个顺序表有序。
// 思路:将A视为已经进行m趟插入排序的表,将后面的n个元素视为待排序元素插入到前面的有序表中。 void Sort(int A[],int m,int n){ for(int i=m+1;i<=m+n;i++){ if(A[i]<A[i-1]) A[0] = A[i]; for(int j=i-1;A[j]>A[0];j--) A[j+1] = A[j]; A[j+1] = A[0]; } } // 也可以使用归并排序 void Merge(int A[],int m,int n){ int *B = (int *)malloc(sizeof(int)*(m+n)); int i; for(i=0;i<=m+n;i++) B[i] = A[i]; int low = 1; int high = m+1; i = 1; while(low<=m && high<=m+n){ if(B[low]<B[high]) A[i++] = B[low++]; else A[i++] = B[high++]; } while(low<=m) A[i++] = B[low++]; while(high<=m+n) A[i++] = B[high++]; } |
03.有一种简单的排序算法,称为计数排序(count sorting)。(这种排序算法对一个待排序的表(用数组表示)进行排序,并将排序结果存放到另一个新的表中。必须注意的是,表中所有待排序的关键码互不相同,计数排序算法针对表中的每个记录,扫描待排序的表一趟,统计表中有多少个记录的关键码比该记录的关键码小,假设针对某个记录统计出的计数值为c,则这个记录在新有序表中的合适存放位置即为c。
// 1)设计实现计数排序的算法。
// 2)对于有n个记录的表,关键码比较次数是多少?
// 3)与简单选择排序相比较,这种方法是否更好?为什么?
ElemType* CountSort(ElemType A[],int n){ ElemType *B = (ElemType *)malloc(sizeof(ElemType)*n); int count = 0; int i,j; for(i=0;i<n;i++){ for(j=0;j<n;j++) if(A[j]<A[i]) count++; B[cout] = A[i]; } return B; } //(2)对于有n个记录的表,每个关键码都要与n个记录含自身进行比较,比较次数n^2。 //(3)简单选择排序好。简单选择排序比较次数n(n-1)/2,只使用一个位置用来交换记录。此方法比较次数n^2,且需要另一组数组空间。 // 对题目进行修改:任意元素之间只允许对比一次。 ElemType* CountSort(ElemType A[],int n){ // 比较部分的代码 int *Count = (int *)malloc(sizeof(int)*n); int i,j; for(i=0;i<n;i++) Count[i] = 0; for(i=0;i<n;i++){ for(j=i+1;j<n;j++) if(A[j]<A[i]) Count[i]++; else Count[j]++; } } |
04.设有一个数组中存放了一个无序的关键序列K1,K2,…,Kn。现要求将Kn放在将元素排序后的正确位置上,试编写实现该功能的算法,要求比较关键字的次数不超过n。
int Partition(ElemType K[],int n){ int low = 0; int high = n-1; ElemType pivot = K[n-1]; while(low<high){ while(low<high && K[low]<=pivot) low++; if(low<high) K[high] = K[low]; // 移动到右端 while(low<high && K[high]>=pivot) high--; if(low<high) K[low] = K[high]; // 移动到左端 } K[low] = pivot; retrun low; } |