数据结构-排序算法总结
2017-07-22 23:13
253 查看
数据结构中排序算法是各大公司面试的常客,下面则是对各种排序算法的总结:
2、内部排序和外部排序:整个排序过程完全在内存中进行,叫做内部排序。数据量较大需要借助外部存储设备才能完成,叫做外部排序。
3、时间复杂度:算法中基本操作重复执行的次数称为算法的时间复杂度,记为T(n)=O(f(n))。
4、空间复杂度:算法所需存储空间的量度称为算法的空间复杂度,记为S(n)=O(f(n)),若排序算法所需的辅助空间并不依赖于问题的规模n,即辅助空间为O(1)。
5、排序的稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。
下面将对各种排序算法逐一进行叙述:
一、插入类排序
思想:在一个已经排好序的序列中,将未被排进的元素按照原先的规定插入到指定位置。
1、 直接插入排序:
思想:最基本的插入排序,将第i个元素插入到前i−1个元素中的适当位置。
时间复杂度:T(n)=O(n2)
空间复杂度:S(n)=O(1)
稳定性:稳定排序
Java实现:
2、 折半插入排序:
思想:折半插入排序是直接插入排序的改进版,由于前半部分为已排好序的数列,这样我们不用按顺序依次寻找插入点,可以采用折半查找的方法来加快寻找插入点的速度。由于折半查找只是减少了比较次数,但是元素的移动次数不变,因此时间复杂度和直接插入排序一样大。
时间复杂度:T(n)=O(n2)
空间复杂度:S(n)=O(1)
稳定性:稳定排序
Java实现:
3、 希尔排序:
思想:希尔排序是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序把待排序序列分成若干较小的子序列,然后逐个使用直接插入排序法排序,最后再对一个较为有序的序列进行一次排序,主要是为了减少移动的次数,提高效率。
时间复杂度:T(n)=O(n32)
空间复杂度:S(n)=O(1)
稳定性:不稳定排序
Java实现:
二、交换类排序
思想:两两比较待排序记录的关键字,发现两记录的次序相反时即进行交换,直到没有反序的记录为止。
1、 冒泡排序:
思想:冒泡排序将待排序的元素看作是竖着排列的“气泡”,较小的元素比较轻,从而要往上浮。
时间复杂度:T(n)=O(n2)
空间复杂度:S(n)=O(1)
稳定性:稳定排序
Java实现:
2、 快速排序:
思想:冒泡排序一次只能消除一个逆序,为了能一次消除多个逆序,采用快速排序,因此快速排序是冒泡排序的一种改进。通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。首先在序列中随机选取一个数字,将大于该数字的放在其右边,小于该数字的放在左边。对于该数字左、右边的序列递归调用该过程。
时间复杂度:T(n)=O(n∗log(n))
空间复杂度:S(n)=O(log(n))
稳定性:不稳定排序
Java实现:
三、选择类排序
思想:选择排序的基本思想是对待排序的记录序列进行n-1遍的处理,第i遍处理是将L[i..n]中最小者与L[i]交换位置。
1、 简单选择排序:
思想:依次遍历序列,每次选出值最小的记录,并和当前所遍历的下标最小的记录进行对换。
时间复杂度:T(n)=O(n2)
空间复杂度:S(n)=O(1)
稳定性:稳定排序
Java实现:
2、 堆排序:
思想:堆排序与快速排序,归并排序一样都是时间复杂度为 O(n*logn)的排序方法。想明白什么是堆排序,得先明白什么是二叉堆:
二叉堆是完全二叉树或者是近似完全二叉树。 二叉堆满足二个特性:
1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的 键值总是小于或等于任何一个子节点的键值时为最小堆。
一般都用数组来表示堆,i 结点的父结点下标就为(i – 1) / 2。它的左右子结点下 标分别为 2 * i + 1 和 2 * i + 2。
堆的插入删除
由于堆也是用数组模拟的,由于堆的根节点是最小的,故堆化数组后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。
时间复杂度:T(n)=O(nlogn)
空间复杂度:S(n)=O(1)
稳定性:不稳定排序
Java实现:
四、归并排序:
1、 归并排序:
思想:该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序,其的基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组的组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。
时间复杂度:T(n)=O(nlogn)
空间复杂度:S(n)=O(n)
稳定性:稳定排序
Java实现:
基本概念
1、排序:排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列2、内部排序和外部排序:整个排序过程完全在内存中进行,叫做内部排序。数据量较大需要借助外部存储设备才能完成,叫做外部排序。
3、时间复杂度:算法中基本操作重复执行的次数称为算法的时间复杂度,记为T(n)=O(f(n))。
4、空间复杂度:算法所需存储空间的量度称为算法的空间复杂度,记为S(n)=O(f(n)),若排序算法所需的辅助空间并不依赖于问题的规模n,即辅助空间为O(1)。
5、排序的稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。
算法分类
排序算法总共可以分4大类,分别是插入类排序、交换类排序、选择类排序、归并类排序。各种排序算法的分类以及时间复杂度和空间复杂度的对比图如下:下面将对各种排序算法逐一进行叙述:
一、插入类排序
思想:在一个已经排好序的序列中,将未被排进的元素按照原先的规定插入到指定位置。
1、 直接插入排序:
思想:最基本的插入排序,将第i个元素插入到前i−1个元素中的适当位置。
时间复杂度:T(n)=O(n2)
空间复杂度:S(n)=O(1)
稳定性:稳定排序
Java实现:
// 直接插入排序 // 时间复杂度:T(n)=O(n^2) // 空间复杂度:S(n) = O(1) // 稳定性:稳定排序 public void directInsertSort(int[] arr){ int temp; //哨兵 int i,j; for(i = 1;i < arr.length;i++){ if(arr[i] < arr[i-1]){ temp = arr[i]; for( j = i-1;j >= 0 && arr[j] > temp;j--) arr[j+1] = arr[j]; arr[j+1] = temp; } } }
2、 折半插入排序:
思想:折半插入排序是直接插入排序的改进版,由于前半部分为已排好序的数列,这样我们不用按顺序依次寻找插入点,可以采用折半查找的方法来加快寻找插入点的速度。由于折半查找只是减少了比较次数,但是元素的移动次数不变,因此时间复杂度和直接插入排序一样大。
时间复杂度:T(n)=O(n2)
空间复杂度:S(n)=O(1)
稳定性:稳定排序
Java实现:
// 折半插入排序 // 时间复杂度:T(n)=O(n^2) // 空间复杂度:S(n) = O(1) // 稳定性:稳定排序 public void binaryInsertSort(int[] arr) { int length = arr.length; int temp; for(int i = 1;i < length;i++){ int midIndex = (0 + i-1)/2; if(arr[i] < arr[midIndex]){ for(int j = 0;j <= midIndex;j++){ if(arr[i] < arr[j]){ temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } } else { for (int j = midIndex; j < i; j++) { if (arr[i] < arr[j]) { temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } } } }
3、 希尔排序:
思想:希尔排序是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序把待排序序列分成若干较小的子序列,然后逐个使用直接插入排序法排序,最后再对一个较为有序的序列进行一次排序,主要是为了减少移动的次数,提高效率。
时间复杂度:T(n)=O(n32)
空间复杂度:S(n)=O(1)
稳定性:不稳定排序
Java实现:
// 希尔排序 // 时间复杂度:T(n)=O(n^1.5) // 空间复杂度:S(n) = O(1) // 稳定性:不稳定排序 public void shellSort(int[] arr) { int temp,i,j; //增量gap,并逐步缩小 // 增量 for(int gap = arr.length/2;gap > 0;gap /= 2){ //从第gap个元素,逐个对其所在组进行直接插入排序操作 for(i = gap;i < arr.length;i++){ for(j = i-gap;j >= 0 && arr[j] > arr[j+gap];j -= gap){ temp = arr[j]; arr[j] = arr[j+gap]; arr[j+gap] = temp; } } } }
二、交换类排序
思想:两两比较待排序记录的关键字,发现两记录的次序相反时即进行交换,直到没有反序的记录为止。
1、 冒泡排序:
思想:冒泡排序将待排序的元素看作是竖着排列的“气泡”,较小的元素比较轻,从而要往上浮。
时间复杂度:T(n)=O(n2)
空间复杂度:S(n)=O(1)
稳定性:稳定排序
Java实现:
// 冒泡排序 // 时间复杂度:T(n)=O(n^2) // 空间复杂度:S(n) = O(1) // 稳定性:稳定排序 public void bubbleSort(int[] arr) { int length = arr.length; int temp; for(int i = 0;i < length;i++){ for(int j = 1;j < length;j++){ if(arr[j] < arr[j-1]){ temp = arr[j]; arr[j] = arr[j-1]; arr[j-1] = temp; } } } }
2、 快速排序:
思想:冒泡排序一次只能消除一个逆序,为了能一次消除多个逆序,采用快速排序,因此快速排序是冒泡排序的一种改进。通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。首先在序列中随机选取一个数字,将大于该数字的放在其右边,小于该数字的放在左边。对于该数字左、右边的序列递归调用该过程。
时间复杂度:T(n)=O(n∗log(n))
空间复杂度:S(n)=O(log(n))
稳定性:不稳定排序
Java实现:
/* // 快速排序 // 时间复杂度:T(n)=O(nlogn) // 空间复杂度:S(n) = O(logn) // 稳定性:不稳定排序 */ void quickSort(int arr[], int start, int end) { if(start < end){//判断start是否等于end,如果等于则说明各区间只有一个数 int i = start; int j = end; int x = arr[start];//将start作为基准数 while(i < j){//如果i=j,则结束 while(i < j && arr[j] >= x) // 从右向左找第一个小于x的数 j--; //此时,arr[j] < x,则令arr[i]=arr[j],且i向前移动一格 if(i < j) arr[i++] = arr[j]; while(i < j && arr[i] < x)// 从左向右找第一个大于等于x的数 i++; //此时,arr[i] >= x,则令arr[j]=arr[i],且j向后移动一格 if(i < j) arr[j--] = arr[i]; } //此时,arr[i]=arr[j]=x,且比x大的数全在它的右边,小于或等于它的数全在其左边。下面则再对其左右区间重复这个步骤 arr[i] = x; quickSort(arr,start,i-1);//对其左边区间重复上述操作 quickSort(arr,i+1,end);//对其右边区间重复上述操作 } }
三、选择类排序
思想:选择排序的基本思想是对待排序的记录序列进行n-1遍的处理,第i遍处理是将L[i..n]中最小者与L[i]交换位置。
1、 简单选择排序:
思想:依次遍历序列,每次选出值最小的记录,并和当前所遍历的下标最小的记录进行对换。
时间复杂度:T(n)=O(n2)
空间复杂度:S(n)=O(1)
稳定性:稳定排序
Java实现:
/* *简单选择排序 * // 时间复杂度:T(n)=O(n^2) // 空间复杂度:S(n) = O(1) // 稳定性:稳定排序 */ void selectSort(int arr[]) { int length = arr.length; int temp; for(int i = 0;i < length;i++){ int min = arr[i]; for(int j = i;j < length;j++){ if(min > arr[j]){ temp = min; min = arr[j]; arr[j] = temp; } } arr[i] = min; } }
2、 堆排序:
思想:堆排序与快速排序,归并排序一样都是时间复杂度为 O(n*logn)的排序方法。想明白什么是堆排序,得先明白什么是二叉堆:
二叉堆是完全二叉树或者是近似完全二叉树。 二叉堆满足二个特性:
1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的 键值总是小于或等于任何一个子节点的键值时为最小堆。
一般都用数组来表示堆,i 结点的父结点下标就为(i – 1) / 2。它的左右子结点下 标分别为 2 * i + 1 和 2 * i + 2。
堆的插入删除
由于堆也是用数组模拟的,由于堆的根节点是最小的,故堆化数组后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。
时间复杂度:T(n)=O(nlogn)
空间复杂度:S(n)=O(1)
稳定性:不稳定排序
Java实现:
/** * 堆排序 */ void heapSort(int[] array) { if (array == null || array.length <= 1) { return; } buildMaxHeap(array); for (int i = array.length - 1; i >= 0; i--) { int temp = array[0]; array[0] = array[i]; array[i] = temp; maxHeap(array, i, 0); } } /** * 创建最大堆 */ void buildMaxHeap(int[] array) { if (array == null || array.length <= 1) { return; } for (int i = array.length / 2; i >= 0; i--) { maxHeap(array, array.length, i); } } /** * 调整最大堆 */ void maxHeap(int[] array, int heapSize, int index) { int left = index * 2 + 1; int right = index * 2 + 2; int largest = index; if (left < heapSize && array[left] > array[index]) { largest = left; } if (right < heapSize && array[right] > array[largest]) { largest = right; } if (index != largest) { int temp = array[index]; array[index] = array[largest]; array[largest] = temp; maxHeap(array, heapSize, largest); } }
四、归并排序:
1、 归并排序:
思想:该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序,其的基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组的组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。
时间复杂度:T(n)=O(nlogn)
空间复杂度:S(n)=O(n)
稳定性:稳定排序
Java实现:
/** * 归并排序 * 时间复杂度:T(n)=O(nlogn) * 空间复杂度:S(n) = O(n) * 稳定性:稳定排序 */ void mergeSort(int[] nums, int low, int high) { int mid = (low + high) / 2; if (low < high) { // 左边 mergeSort(nums, low, mid); // 右边 mergeSort(nums, mid + 1, high); // 左右归并 merge(nums, low, mid, high); } } void merge(int[] nums, int low, int mid, int high) { int[] temp = new int[high - low + 1]; int i = low;// 左指针 int j = mid + 1;// 右指针 int k = 0; // 把较小的数先移到新数组中 while (i <= mid && j <= high) { if (nums[i] < nums[j]) { temp[k++] = nums[i++]; } else { temp[k++] = nums[j++]; } } // 把左边剩余的数移入数组 while (i <= mid) { temp[k++] = nums[i++]; } // 把右边边剩余的数移入数组 while (j <= high) { temp[k++] = nums[j++]; } // 把新数组中的数覆盖nums数组 for (int k2 = 0; k2 < temp.length; k2++) { nums[k2 + low] = temp[k2]; } }
相关文章推荐
- 数据结构-排序算法总结与感悟
- 各种排序(数据结构复习之内部排序算法总结)
- 数据结构各种排序算法总结
- 数据结构-各类排序算法总结(三)
- 【数据结构】排序算法总结及php排序算法实现代码(伪代码见 http://blog.sina.com.cn/s/blog_676a011e0100ty5o.html)
- 常用数据结构之排序算法总结
- 算法与数据结构-常用排序算法总结2-基数排序
- 数据结构笔记--总结各种排序算法及其应用
- 数据结构总结-排序算法-冒泡算法
- 数据结构---九大排序算法再总结
- 数据结构-排序算法的总结
- 数据结构与算法总结4_排序算法
- 数据结构-各类排序算法总结
- 算法与数据结构-常用排序算法总结1-比较排序
- 数据结构--排序算法总结代码
- [数据结构]九大基础排序算法总结
- 算法与数据结构-常用排序算法总结2-计数排序
- 总结数据结构中重要的排序算法
- 数据结构-排序算法总结
- 数据结构复习之排序算法的总结回顾