排序算法总结
来源:互联网 发布:快压 mac版 怎么用 编辑:程序博客网 时间:2024/06/01 12:17
排序算法学习总结
本文中的思路代码皆借鉴于《大话数据结构》
冒泡排序法
算法描述:
给一个待排序的数组,固定其中一端,从另一端开始两两比较,如果反序则交换,直到没有反序的记录为止。在此过程中,数字较小(或较大)的慢慢从数组的一端向另一端移动,如同气泡慢慢浮上水面。
代码实现:
boolean flag = true;public void sort() { for (int i = 0; i < array.length - 1 && flag; i++) { //每次循环初始为false,若在一个循环里没有发生数据交换,则排序结束。 flag = false; for (int j = array.length - 1; j > i; j--) { if (array[j - 1] > array[j]) { Swap.swap(array, j - 1, j); flag = true; } } }}
复杂度分析:
- 对于长度为N的数组,在初始有序的情况下,循环次数为N-1,交换次数为0;
- 初始反序的情况下,循环次数为(N-1)N/2次,交换次数为(N-1)N/2;
简单选择排序
算法描述:
每一次循环在n-i+1个记录里选取最小的记录,并和第i个记录交换,作为有序序列的第i个记录。
代码实现:
public void sort( ){ int min = 0; for(int i = 0;i<length();i++){ min = i; for(int j = i+1;j<length();j++){ if(array[min]>array[j]){ min = j; } } if(min!=i){ Swap.swap(array, min, i); } }}
复杂度分析:
- 给定一个长为N的数组,若初始为有序,则循环次数为(N-1)N/2,交换次数为0;
- 若初始反序,则循环次数为(N-1)N/2,交换次数为N/2;
直接插入排序
算法描述:
将一个数据插入到已经排序好的有序数组中。
代码实现:
public void sort(){ int temp = 0; int i,j=0; for(i = 1;i<length();i++){ if(array[i]<array[i-1]){ temp = array[i];//记录待插入的数 for(j = i-1;j>=0&&array[j]>temp;j--){ array[j+1] = array[j];//比待插入的数大的往后移 } array[j+1] = temp;//将待插入的数插入正确的位置 } }}
复杂度分析:
- 给定一个长为N的数组,若初始为有序,则比较次数为N-1,移动次数为0;
- 若初始反序,则比较次数为(N-1)+(N-1)N/2=(N+2)(N-1)/2次,移动次数为(N+2)(N-1)/2次。
希尔排序
算法描述:
将待排序数组分成几块,对每一块进行直接插入排序,当整个序列基本有序后,再进行一次总的直接插入排序。
代码实现:
public void sort() { int i, j = 0; int temp = 0; //increment为增量,初始为数组长度,代表将每个数据看做一个子块,再在每次循环中减小increment的大小,即将数组看成若干块,直至为1循环结束,排序结束 int increment = length(); do { increment = increment / 3 + 1;//经验公式 for (i = increment ; i < length(); i++) { if (array[i - increment] > array[i]) { temp = array[i]; for (j = i - increment; j >= 0 && array[j] > temp; j -= increment) { array[j + increment] = array[j]; } array[j + increment] = temp; } } } while (increment > 1);}
复杂度分析:
时间复杂度为O(n^(3/2))
堆排序
算法分析:
堆排序就是利用堆(如大顶堆)进行排序的方法。它的基本思想是将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将其与序列最后一个数交换,此时最后一个数就是最大值。然后将剩余n-1个数构造成一个大顶堆,重复上述过程,完成排序。
代码实现:
/*** * 堆调整 * @param node 待调整的节点 * @param end 堆的最后一个节点 */private void heapAdjust(int node , int end){ int temp = array[node]; //对于节点i,若有子节点,则第2i+1个节点为其左节点,2(i+1)个节点为其右节点 for(int i = node*2+1;i<end;i=2*i+1){ if(i<end-1&&array[i]<array[i+1]) i++;//若右节点较大,则与右节点互换 if(temp>array[i]) break;//若根节点更大,则不调整,结束循环 array[node] = array[i]; node = i; } array[node] = temp;}public void sort(){ //构建大顶堆 for(int i = length()/2-1;i>=0;i--){ heapAdjust(i,length()); } //排序 for(int j = length()-1;j>0;j--){ Swap.swap(array, j, 0); heapAdjust(0,j); }}
复杂度分析:
- 构建堆时,每个非终端节点最多进行两次比较和互换,因此构建堆的时间复杂度为O(n);
- 排序时,第i次取堆顶记录重建堆要用O(logi)的时间(完全二叉树的某节点i到根节点的距离为logi+1),并且需要取n-1次堆顶数据,因此时间复杂度为O(nlogn);
- 故总体来说,堆排序的时间复杂度为O(nlogn)。
归并排序
算法分析:
假设初始序列有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2个长度为2或1的有序子序列,再两两归并……直至得到一个长度为n的有序序列为止,此为2路归并排序。
代码实现:
public void sort() { mergeSort(array, array, 0, length() - 1);}//将sr[s..t]归并排序为tr1[s..t]public void mergeSort(int[] sr, int[] tr1, int s, int t) { int m = (s + t) / 2;//将sr[s..t]平分为sr[s..m]和sr[m+1..t] int[] tr2 = new int[length()]; if (s == t) { tr1[s] = sr[s]; } else { mergeSort(sr, tr2, s, m); mergeSort(sr, tr2, m + 1, t); merge(tr2, tr1, s, m, t);//将tr[s..m]和sr[m+1..t]归并待tr1[s..t] }}public void merge(int[] sr, int[] tr, int s, int m, int t) { int j, k, l; //将sr中记录由小到大归并到tr for (j = m + 1, k = s; s <= m && j <= t; k++) { if (sr[s] < sr[j]) { tr[k] = sr[s++]; } else { tr[k] = sr[j++]; } } //将剩余的sr[s..m]复制到tr if (s <= m) { for (l = k ; l <= t; l++) { tr[l] = sr[s++]; } } //将剩余的sr[j..n]复制到tr if (j <= t) { for (l = k ; l <= t; l++) { tr[l] = sr[j++]; } }}
复杂度分析:
- 一趟归并需要将sr[0]~sr[n-1]中相邻的长度为h的有序序列进行两两归并。并将结果放到tr[0]~tr[n-1]中,这需要将待排序列中的所有记录扫描一遍,因此耗费O(n)时间;
- 由完全二叉树的深度可知,整个归并排序需要进行logn次;
- 故总的时间复杂度为O(nlogn)。
- 归并过程中需要与待排序序列同样大小的存储空间以及递归深度为logn的栈空间,因此空间复杂度为O(n+logn)。
- 此外,因为需要两两比较,不存在跳跃,因此是一种稳定的排序算法。
归并排序的非递归实现:
public void sort(int[] tr) { int k = 1; while (k < length()) { mergePass(array, tr, k, length() - 1); k = k << 1; mergePass(tr, array, k, length() - 1); k = k << 1; }}public void mergePass(int[] sr, int[] tr, int s, int n) { int i = 0; while (i < n - 2 * s + 1) { merge(sr, tr, i, i + s - 1, i + 2 * s - 1);//两两归并 i = i + 2 * s; } if (i <= n - s + 1) {//归并最后两个序列 merge(sr, tr, i, i + s - 1, n); } else {//最后只剩一个序列 for (int j = i; j < n; j++) { tr[j] = sr[j]; } }}
复杂度分析:
- 非递归的迭代方法,避免了递归时深度为logn的栈空间,因此空间复杂度为O(n),避免地柜时间性能上也有一定的提升(递归时函数进出栈需要消耗时间)。
快速排序
算法分析:
通过一趟排序将待排序记录分割成独立的两部分,其中一部分均比另一部分小,则可分别对这两部分继续进行排序,直至整个序列有序。
代码实现:
public void sort(){ qSort(0, length()-1);}protected void qSort(int low , int high){ int pivot; if(low<high){ pivot = partition(low,high);//将array[low..high]分为两部分,在枢纽值pivot之前的都比其小,在其后的都比其大 qSort(low, pivot-1); qSort(pivot+1, high); }}protected int partition(int low , int high){ int pivotKey = array[low];//将待排序部分的第一个值作为枢纽值 while(low<high){//从两端交替向中间扫描 while(low<high&&array[high]>pivotKey) high--; Swap.swap(array, low, high);//将比枢纽值小的交换到前端 while(low<high&&array[low]<=pivotKey) low++; Swap.swap(array, low, high);//将比枢纽值大的交换到后端 } //最终low=high,返回枢纽值 return low;}
复杂度分析:
- 在最优情况下,即每次枢纽值都能将待排序序列平分两半,时间复杂度为O(nlogn),递归造成的栈空间为递归树的深度logn,即空间复杂度为O(logn);
- 在最坏情况下,即待排序序列为正序或倒序,时间复杂度为O(n^2),需要进行n-1次调用,空间复杂度为O(n);
- 平均情况下,时间复杂度为O(nlogn),空间复杂度为O(logn);
- 由于关键字的比较和交换是跳跃进行的,所以快速排序是一种不稳定的排序方法。
优化:
- 枢纽选取优化:取头、尾和中间值中的中间值
int mid = low+(high-low)/2;if(array[low]>array[high])//左值较小 Swap.swap(array, low, high);if(array[mid]>array[high])//右值最大 Swap.swap(array, mid, high);if(array[mid]>array[low])//中值最小 Swap.swap(array, mid, high);int pivotKey = array[low];
- 不必要的交换优化:替换操作取代交换操作
int pivotKey = array[low];while(low<high){ while(low<high&&array[high]>pivotKey) high--; array[low] = array[high]; while(low<high&&array[low]<=pivotKey) low++; array[high] = array[low];}array[low] = pivotKey;
- 优化递归操作:尾递归
protected void qSort(int low , int high){ int pivot; while(low<high){//if改成了while pivot = partition(low,high); qSort(low, pivot-1); low = pivot+1;//尾递归 //qSort(pivot+1, high); }}
7种算法的指标:
- 算法--排序算法总结
- 算法:排序算法总结
- 算法:排序算法总结
- 算法-排序算法总结
- 算法-排序算法总结
- 【排序算法】排序算法总结
- 排序算法总结---希尔排序
- 排序算法总结---冒泡排序
- 排序算法总结----快速排序
- 排序算法总结---希尔排序
- 排序算法总结【内排序】
- 排序算法之内排序总结
- 排序算法总结:冒泡排序
- 【排序算法总结】冒泡排序
- 【排序算法总结】选择排序
- 排序算法总结
- 排序算法大总结
- 排序算法总结
- SkyNet + Lua 学习教程
- 每天一个Linux命令(53):netstat
- vi 查找替换使用方法
- 求全排列
- FITness自动化测试框架
- 排序算法总结
- 最短路径-Dijkstra算法和Floyd算法
- Sublime Text安装package control
- 剑指offer-面试题09-斐波那契数列
- Javascript操作DOM常用API总结
- 八大排序算法
- git 指令及用法
- ViewTreeObserver 监听整个视图发生的变化情况
- mysql 5.5多实例安装