[小明学算法]5.常用排序算法
一.选择排序
选择排序是比较容易理解的排序方法,从未排序的数组中,选择一个最大的元素,放到队首,循环操作,就是选择排序.
操作步骤:
1.设i=0
2.找到数组a[i...length-1]中最大的,记录数组下标值
3.将数组的a[i]与最大值交换位置,则a[0...i]是从大到小的排序序列
4.i<length,i=i+1,重复执行2,否则排序结束
代码:
void Selection(int a[], int length) { for (int i = 0; i < length; i++) { //找到数组a[i...length-1]中最大的,记录数组下标值 int max = a[i], key = i; for (int j = i; j < length; j++) { if (a[j] > max) { max = a[j]; key = j; } } //将数组的a[i]与最大值交换位置,则a[0...i]是从大到小的排序序列 swap(a[i], a[key]); } }
二.堆排序
堆排序本质也是一种选择排序.只是选择的方法并不是通过遍历的方式来找到最大(小)值.
堆排序的选择是使用了一种叫做二叉堆的数据结构.二叉堆的特点是保证父节点比它所有的子节点都大(或者小).所以根据二叉堆的性质,每次选取它的根节点,放入到排序序列即可.堆排序操作就是一个将堆不停的删除堆顶元素,直到堆为空的操作.
操作步骤:
1.建堆
2.删除堆顶元素,直到堆为空.
以上就是堆排序的总的思路,具体操作时,建堆和选择堆顶元素都有一些小技巧的使用.
二叉堆是一个类二叉完全树的结构,我们用数组a来表示.我们这次建立一个最小堆,即父节点都小于其子节点的堆.建堆的时候,每次在堆中增加一个元素,放在最后位置,然后与其所有的父节点依次比较大小,若法线有比它大的父节点,则交换位置.
1.建堆
①设i=0
②将数组a[i]元素增加到堆a[0...i]中
③若i=i+1,若i<length,重复步骤②,否则结束建堆,建堆完成
建堆完成之后就可以进行堆排序了,之前已经说过,堆排序的本质就是取得堆顶元素.问题是,取得堆顶元素之后,堆剩下的部分就不在是一个符合所有父节点都大于子节点的二叉堆了,我们还要对剩下的元素再进行一遍建堆,很是麻烦.所以这里有一种小技巧的删除堆顶元素的操作,操作步骤如下:
2.删除堆顶元素,直到堆为空.
①设i=0,将堆顶元素a[0]与堆尾元素a[n-1]交换位置
②将堆的长度n减1
③令a[j]为a[i]值较小的子节点,若a[j]不存在,执行⑤
④若a[i]>a[j],则交换a[i]与a[j],且令i=j,就是将a[i]下移了,重复第③步.否则,执行⑤
⑤若n>0,执行①,否则结束.
以上五步中,①到④是删除堆顶元素的操作,①到⑤是删除堆内所有元素的操作.全部删除完之后,因为每次删除的堆顶都被放置在了堆数组的末尾,则得到了一个有序列的数组,即堆排序.
代码:
//在堆为增加一个节点,然后对堆进行排序 void MinHeapFixup(int a[], int i) { //父节点总比子节点小,如果父节点比子节点大,则交换 for (int j = (i - 1) / 2; j >= 0 && a[j] > a[i]; i = j, j = (j - 1) / 2) swap(a[i], a[j]); } //修复堆 void MinHeapFixDown(int a[], int length) { int i, j; i = 0; j = 2 * i + 1; while (j<length) { //左右孩子都要找,找到两个孩子中小的那个 if (j + 1 < length&&a[j + 1] < a[j]) j++; //如果没有比它小的,结束 if (a[j] >a[i]) break; //否则,交换 swap(a[i], a[j]); i = j; j = 2 * i + 1; } } //堆只能删除顶节点,删除之后还要修复 void MinHeapDelete(int a[], int length) { //交换 swap(a[0], a[length - 1]); //修复 MinHeapFixDown(a, length - 1); } void HeapSort(int a[], int length) { cout << "建堆" << endl; for (int i = 0; i < length; i++) { MinHeapFixup(a, i); Print(a, length); } cout << endl; cout << "排序,即删除堆顶元素" << endl; for (int i = 0; i < length; i++) { MinHeapDelete(a, length - i); Print(a, length); } cout << endl; Print(a, length); } void main() { int arr[7] = { 1, 55, 6, 16, 15, 122, 76 }; int b[7]; HeapSort(arr, 7); }
三.冒泡排序
冒泡排序是比较常见的排序算法,它与选择排序有些类似,每一趟排序都将第几位所对应的值确定.
但是它不记录当前的极值,遍历时,每碰见一个就进行比较,只在极值变化时,进行交换操作.
操作步骤:
1.设i=0
2.设j=i
3.若a[i]>a[j],交换a[i]与a[j].
4.j=j+1;若j<length,重复3,否则,执行5
5.i=i+1;若i<length,重复2,否则,结束
代码:
void Bubble(int a[], int length) { //遍历数组 for (int i = 0; i < length; i++) { //将a[i]与a[i...length]内所有元素比较 for (int j = i; j < length; j++) { //若有比a[i]小的,交换 if (a[i] > a[j]) { int t = a[j]; a[j] = a[i]; a[i] = t; } } } cout << "冒泡:"; Print(a, 7); cout << endl; }
四.快速排序
快速排序每一趟排序之后,当前排序值v都会处在目标位置j上,其左侧都是比它大(小)的值,右侧都是比它小(大)的值.所以一趟排序之后,递归的分别对左右两侧再进行排序即可.快排与冒泡一样都属于交换排序,但在目标排序序列无序时,快排效果好,若有序的话就与冒泡的时间复杂度一样.
快排与堆排序以及归并排序都是较快的排序算法.
操作步骤:
1.得到数组a,以及排序起始点low,终点height,如果low不小于height,结束,否则执行2
2.从第height个元素开始,从后往前找,找到第一个比a[low]小的,交换
3.从第low个元素开始,从前往后找,找到第一个比a[height]大的,交换
4.若low<height,重复2,3,否则,令privotLoc为low,执行5
5.令height等于privotLoc,执行1
6.令low等于privotLoc+1,执行1
代码:
int partition(int a[], int low, int height) { //完成之后,a[low]元素左侧就全部都是比它大的,右侧都是比它小的 while (low<height) { //从最开始的元素开始,从后往前找,找到第一个比它小的,交换 while (a[low]>a[height]) height--; swap(a[low], a[height]); //从开始往后找,找到第一个比它大的,交换 while (a[height] < a[low]) low++; swap(a[low], a[height]); } Print(a, 7); //返回当前最低的那个元素排序后的位置 return low; } void Quick(int a[], int low, int height) { if (low >= height) return; int privotLoc = partition(a, low, height); Quick(a, low, privotLoc - 1); Quick(a, privotLoc + 1, height); } void main() { int arr[7] = { 1, 55, 6, 16, 15, 122, 76 }; Quick(arr, 0, 6); }
五.归并排序
归并排序的基础对两个有序队列进行排列,然后将无需队列分割为n个有序队列,向上归并,得到最后排序队列.
归并排序需要用到两个数组,即从数组a将所有数据归并到数组b.
操作步骤:
1.令每个子组长度为len=1
2.对长度为len的子数组两两进行归并到数组b
3.交换数组a与数组b
3.令len=len*2,单个组的长度增倍,即合并后每个小组的长度.如果len<length,重复2,3,否则,结束
代码:
//将数组a[i,m-1]和a[m,n] 归并到b[i,n] void Merge(int a[], int b[], int i, int m, int n) { int t = i, k = m;//第一组和第二组的计数器 int j = i;//数组b的计数器 //遍历第二组内数据,若第一组内有比当前值小的,将第一组数放到数组b内,否则,将自己放入数组b while (k<n + 1) { while (a[t] <a[k] && t<n) b[j++] = a[t++]; b[j++] = a[k++]; } //遍历第一组剩余数据,将其放入数组b while (t<m) b[j++] = a[t++]; for (; i <= n; i++) { cout << b[i] << " "; } cout << endl; } void MergeSort(int a[], int b[], int length) { int len = 1; while (len<length) { //将数组分为长度为len个等长组,对每个a[i...i+len-1]以及a[i+len...i+2*len-1]进行合并. int i = 0; while (i + 2 * len<length) { Merge(a, b, i, i + len, i + 2 * len - 1); //跳转过以归并的两组下表,对下一个两组进行归并 i += 2 * len; } //对结尾不等长的组进行合并 if (i + len <= length) { Merge(a, b, i, i + len, length - 1); } //交换a和b,保证下次还是从a归并到b swap(a, b); //单个小组的长度增倍 len *= 2; } }