您的位置:首页 > 健康 > 美食 > 数据结构_排序

数据结构_排序

2024/10/5 20:23:49 来源:https://blog.csdn.net/m0_73620971/article/details/139751785  浏览:    关键词:数据结构_排序

目录

一、排序

二、插入排序

2.1 直接插入排序

2.2 希尔排序

三、选择排序

3.1 直接选择排序

3.2 堆排序

四、交换排序

4.1 冒泡排序

4.2 快速排序

五、归并排序

六、排序算法分析

总结


一、排序

排序:就是使一串记录序列,按照其中某个或某些关键字的大小,进行递增或递减的排列操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同关键字的记录,若经过排序后,这些记录的相对次序保持不变,则称这种排序算法是稳定的;否则称为不稳定。

例如,在原序列中,r[i] = r[j],且 r[i] 在 r[j] 之前,若排序后的序列中,r[i] 仍在 r[j] 之前,则排序算法稳定,反之即不稳定。


二、插入排序

2.1 直接插入排序

核心思想:第二个元素开始遍历,每次遍历将该元素与其前方元素对比,做出相应交换。

    public static void insertSort(int[] array) {//初始 i 下标指向第二个元素for (int i = 1; i < array.length; i++) {//将 i下标的值放入 temp 中int temp = array[i];//初始 j 下标指向 i 下标的前一位int j = i-1;for (; j >= 0; j--) {//若 j 下标的值比 temp 中的值大,则将其往后移一位if (array[j] > temp) {array[j+1] = array[j];} else {break;}}//循环结束将原本 i 的值放至 j 下标的后一位中array[j+1] = temp;}}

【总结】

时间复杂度:

        最坏情况下:O(n^2)

        最好情况下:O(n)

空间复杂度:O(1)

稳定性:稳定

适用性:待排序序列接近有序


2.2 希尔排序

希尔排序也叫缩小增量排序基本思想:选定一个整数 gap,将待排序序列分为 gap 组,所有距离为 gap 的元素为同一组,每组分别进行直接插入排序;然后缩小 gap,继续分组排序,直至 gap = 1 时,所有元素为一组,最后进行一次直接插入排序。

    public static void shellSort(int[] array) {int gap = array.length;//gap>1 都属于预排序while (gap > 1) {//取 gap 为全部元素的一半gap /= 2;//每组排序shell(array, gap);}}private static void shell(int[] array, int gap) {//初始 i 下标指向 gap,即每组的第二个元素for (int i = gap; i < array.length; i++) {//将 i下标的值放入 temp 中int temp = array[i];//初始 j 下标指向 i 下标的前 1gap 位//j 每次往前 1gap 位int j = i-gap;for (; j >= 0; j-=gap) {//若 j 下标的值比 temp 中的值大,则将其往后移 1gap 位if (array[j] > temp) {array[j+gap] = array[j];} else {break;}}//循环结束将原本 i 的值放至 j 下标的后 1gap 位中array[j+gap] = temp;}}

【总结】 

1、希尔排序是对直接插入排序的优化。

2、当 gap>1 时,都是预排序,是为了让序列趋于有序。

3、稳定性:不稳定


三、选择排序

3.1 直接选择排序

核心思想:遍历每个元素,每次遍历确定一个最小值令其有序。

    public static void selectSort(int[] array) {//遍历每个元素for (int i = 0; i < array.length; i++) {//初始化 minIndex 用于指向最小值int minIndex = i;//i 下标与后方每个元素对比for (int j = i+1; j < array.length; j++) {//确保 minIndex 指向 i 下标及其后方元素的最小值if (array[j] < array[minIndex]) {minIndex = j;}}//交换 i 下标与 minIndex 下标的值int temp = array[i];array[i] = array[minIndex];array[minIndex] = temp;}}

【总结】

时间复杂度:O(n^2)

空间复杂度:O(1)

稳定性:不稳定


3.2 堆排序

① 创建大根堆,初始化 end = array.length-1,即 end 指向最后一个结点;

② 将栈顶元素交换至 end 下标。由于大根堆中栈顶元素最大,故交换一次,end--,保证每次交换后的栈顶元素位置不变;

③ 重新向下调整为大根堆;

④ 重复 ②、③ 操作,直至排序完成。

    //创建大根堆private static void createHeap(int[] array) {//从最后一棵子树开始调整,依次往前,直至根结点//父亲结点 = (孩子结点-1) / 2//usedSize-1 是树中最后一个结点for (int parent = (array.length-1-1) / 2; parent >= 0; parent--) {//向下调整siftDown(array, parent,array.length);}}//堆排序public static void heapSort(int[] array) {//创建大根堆createHeap(array);int end = array.length-1;while (end > 0) {//将大根堆中栈顶元素交换至 endswap(array, 0, end);//向下调整为大根堆siftDown(array, 0, end);//保证每次调整的栈顶元素位置不变end--;}}//向下调整private static void siftDown(int[] array, int parent, int length) {//左孩子结点 = 父亲结点*2 + 1int child = parent*2 + 1;//首先保证该结点有左孩子结点while (child < length) {//再次确定该结点有右孩子结点,再进行比较//保证 child 引用指向孩子结点中最大值if ((child+1) < length && array[child] < array[child+1]) {//若右孩子的值大于左孩子,则 child 引用指向右孩子child = child + 1;}if (array[child] > array[parent]) {//若 child 比 parent 大,则交换元素swap(array, child, parent);//parent 指向 child 位置,向下调整至下方无子树parent = child;child = parent*2 + 1;} else {//子结点都比父结点小break;}}}//交换元素private static void swap(int[] array, int i, int j) {int temp = array[i];array[i] = array[j];array[j] = temp;}

【总结】 

时间复杂度:O(n*log n)

空间复杂度:O(1)

稳定性:不稳定


四、交换排序

4.1 冒泡排序

核心思想:进行 array.length-1 趟比较,每次确定一个最大值元素。

    public static void bubbleSort(int[] array) {//i 表示趟数for (int i = 0; i < array.length-1; i++) {//设置一个标签,检测该趟是否发生交换boolean flag = false;//j 表示该趟比较次数for (int j = 0; j < array.length-1-i; j++) {//每趟确定一个最大值if (array[j] > array[j+1]) {int temp = array[j];array[j] = array[j+1];array[j+1] = temp;//若交换,则改变标签状态flag = true;}}//若标签状态未改变,则序列已经有序if (!flag) {break;}}}

【总结】

时间复杂度:O(n^2),若加上 flag 优化,则最好可以达到 O(n)

空间复杂度:O(1)

稳定性:稳定


4.2 快速排序

1、Hoare版

核心思想: 0 下标元素为基准,保证每次排序后,基准左侧元素小于基准右侧大于基准,分别在左序列与右序列进行递归排序。

    public static void quickSort(int[] array) {quick(array, 0, array.length-1);}private static void quick(int[] array, int start, int end) {// start < end 时才需要排序if (start >= end) {return;}//找到基准的下标int pivot = partitionHoare(array, start, end);//左序列quick(array, start, pivot-1);//右序列quick(array, pivot+1, end);}private static int partitionHoare(int[] array, int left, int right) {//保存基准的值int temp = array[left];//保存基准的下标int index = left;while (left < right) {//在右侧找到比基准小的数while (left < right && array[right] >= temp) {right--;}//在左侧找到比基准小的数while (left < right && array[left] <= temp) {left++;}swap(array, left, right);}//保证基准左侧都比基准小,右侧都比基准大swap(array, index, left);//确定基准的位置时,left = rightreturn left;}

2、挖坑版

核心思想: 0 下标元素为基准,0 下标处为第一个坑位,从右侧开始遍历,找到比基准小的元素放至 left 下标 (初始即 0 下标) 处;然后从左侧遍历到比基准大的放至 right 下标处,循环至 left 与 right 相遇,将基准放入最后一个坑位。

    public static void quickSort(int[] array) {quick(array, 0, array.length-1);}private static void quick(int[] array, int start, int end) {// start < end 时才需要排序if (start >= end) {return;}//找到基准的下标int pivot = partition(array, start, end);//左序列quick(array, start, pivot-1);//右序列quick(array, pivot+1, end);}private static int partition(int[] array, int left, int right) {//保存基准的值int temp = array[left];//保存基准的下标int index = left;while (left < right) {//在右侧找到比基准小的数while (left < right && array[right] >= temp) {right--;}//找到放置左边的坑位array[left] = array[right];//在左侧找到比基准小的数while (left < right && array[left] <= temp) {left++;}//找到放置右边的坑位array[right] = array[left];}//将基准值放入最后一个坑中array[left] = temp;//确定基准的位置时,left = rightreturn left;}

【总结】 

时间复杂度:O(n*log n)

空间复杂度:O(log n)

稳定性:不稳定


五、归并排序

将两个有序序列合并成一个有序序列的操作,称为二路归并

核心步骤:先分解再合并

    public static void mergeSort(int[] array) {merge(array, 0, array.length-1);}private static void merge(int[] array, int start, int end) {// start < end 时才需要排序if (start >= end) {return;}//标记序列中间下标int mid = (start+end)/2;//左序列merge(array, start, mid);//右序列merge(array, mid+1, end);//排序, 合并mergeMethod(array, start, mid, end);}private static void mergeMethod(int[] array, int left,int mid, int right) {//指向左右序列最小值int min1 = left;int min2 = mid+1;//定义一个新的空间用于排序int[] sort = new int[right-left+1];//新空间的下标int k = 0;//保证左右序列都有元素while (min1 <= mid && min2 <= right) {//左右序列从最左侧(最小值)开始比较if (array[min1] <= array[min2]) {sort[k++] = array[min1++];} else {sort[k++] = array[min2++];}}//表示右序列中无元素,即左序列剩下元素比右序列都要大while (min1 <= mid) {sort[k++] = array[min1++];}//表示左序列中无元素,即右序列剩下元素比左序列都要大while (min2 <= right) {sort[k++] = array[min2++];}//将排序好的数组,拷贝回原数组for (int i = 0; i < sort.length; i++) {//利用 left 下标(每个序列起始下标) 保证右序列可以顺利拷贝array[i+left] = sort[i];}}

【总结】 

时间复杂度:O(n*log n)

空间复杂度:O(n)

稳定性:稳定

适用性:更多是在解决磁盘中的外排序问题


六、排序算法分析

排序方法最好平均最坏空间复杂度稳定性
冒泡排序O(n)O(n^2)O(n^2)O(1)稳定
插入排序O(n)O(n^2)O(n^2)O(1)稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
希尔排序O(n)O(n^1.3)O(n^2)O(1)不稳定
堆排序O(n*log n)O(n*log n)O(n*log n)O(1)不稳定
快速排序O(n*log n)O(n*log n)O(n^2)O(log n) ~ O(n)不稳定
归并排序O(n*log n)O(n*log n)O(n*log n)O(n)稳定

总结

1、希尔排序是对直接插入排序的优化。

2、直接选择排序效率不高,很少使用;堆排序效率高。

3、快速排序综合性能和使用场景都是比较好的。

4、归并排序更多是在解决磁盘中的外排序问题。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com