十大排序算法
(前言:嗯,之前是学过一点排序算法的,比如说快排,归并,插入排序,冒泡排序什么的,但是有好多学校也没教,自己学的也不扎实,这次自己一边慢慢写,一边教自己一边,努力把这些搞懂,但我肯定一天是写不完的,所以要多等)
1 算法分类
1.1 比较排序
1.2 非比较排序
复杂度
2023年08月30日 16:50:20 续
2 十大排序算法
2.1 冒泡排序
就不搞那些虚的了,写的前面那些也要不都是百度贴的
冒泡排序,我的印象里的就是两两一次对比换顺序,时间复杂度好高,思想倒是挺简单的,但是代码要让我自己写也不一定写多好,但别的文章里的图都挺好的,还总出现,所以可以直接引用一波
啊,对时间复杂度还是要说一下的
时间复杂度: 最佳情况:T(n) = O(n) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(n2)
//啊,如我所说,很简单
public static int[] bubbleSort(int[] array) {
if (array.length == 0)
return array;
for (int i = 0; i < array.length; i++)
for (int j = 0; j < array.length - 1 - i; j++)
if (array[j + 1] < array[j]) {
int temp = array[j + 1];
array[j + 1] = array[j];
array[j] = temp;
}
return array;
}
2.2 选择排序
先贴个图
2.2.1原理
遍历一遍找到未排序的最小的,再从剩余的里遍历找最小的放到起始位置(第二遍的起始位置),之后同理
2.2.2复杂度
时间复杂度:最佳情况:T(n) = O(n2) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(n2)
2.2.3代码
代码都差不多,我就粘一下
public static int[] selectionSort(int[] array) {
//数组长度为零情况
if (array.length == 0)
return array;
//遍历找最小
for (int i = 0; i < array.length; i++) {
int minIndex = i;
//依次向前
for (int j = i; j < array.length; j++) {
if (array[j] < array[minIndex]) //找到最小的数
minIndex = j; //将最小数的索引保存
}
int temp = array[minIndex];
array[minIndex] = array[i];
array[i] = temp;
}
return array;
}
2023年08月31日 19:09:29
2.3 插入排序
2.3.1 原理
举例:整理桥牌,将每一张牌插入到其他已经有序的牌的适当位置,为了给要插入的元素腾出空间,需要将其余所有元素再插入前都右移动一位
个人理解*:就是从第一个开始,往前遍历,找到自己第一个大于的,然后把这个位置之后的元素后移一位,把自己插入进来,嗯
2.3.2 复杂度
最佳情况:T(n) = O(n) 最坏情况:T(n) = O(n2) 平均情况:T(n) = O(n2)
2023年09月01日 14:54:12
2.3.3 代码
public class Insertion{
public static void main(String[]args){
int N = a.length;
for(int i=0; i < N; i++){
for(int j=i; j>0 && less(a[j], a[j-1]); j--){
exch(a, j, j-1);
}
}
}
private static boolean less(Comparable v, Comparable w){
return v.compareTo(w) < 0;
}
private static boolean exch(Comparable[] a, int i, int j){
Comparable t=a[i]; a[i]=a[j]; a[j]=t;
}
}
再来个简洁版本
public class Insertion {
public static void main(String[] args) {
Comparable[] a = {9, 5, 2, 7, 1};
int N = a.length;
for (int i = 0; i < N; i++) {
Comparable temp = a[i];
int j = i;
while (j > 0 && temp < a[j - 1]) {
a[j] = a[j - 1];
j--;
}
a[j] = temp;
}
for (int i = 0; i < a.length; i++) {
Comparable num = a[i];
System.out.print(num + " ");
}
}
}
2.3.3.1 关于代码的一些问题
- 当使用.compareTo()进行比较时,它会返回一个整数值,用于表示两个对象之间的关系:如果a.compareTo(b)的结果小于0,表示a小于b。如果a.compareTo(b)的结果等于0,表示a等于b。如果a.compareTo(b)的结果大于0,表示a大于b。
- Comparable num : a 这是什么意思? Comparable num : a 是 Java 中的增强 for 循环语法,用于遍历数组 a 中的元素,并将每个元素赋值给变量 num。跟for循环差不多。
- 为什么要用Comparable呢? 使用 Comparable 是为了实现通用的比较功能。Comparable 是一个接口,它定义了一个 compareTo 方法,用于比较两个对象的大小关系。 通过使用 Comparable,我们可以在排序算法中比较不同类型的对象,而不仅仅局限于整数或字符串。这使得代码更加灵活和通用,可以适用于各种类型的数据。 在这段代码中,数组 a 中的元素被声明为 Comparable 类型,这意味着数组中的元素必须实现 Comparable 接口,并提供 compareTo 方法的实现。这样,我们可以使用 compareTo 方法来比较数组中的元素,并根据比较结果进行排序。
插入排序总结
熟悉原理之后还是挺简单的嘛,但是可能是java基础不好,书里的一些例子方法我都不懂,而且懂了原理代码实现的能力也比较弱。加油吧
2.4 希尔排序
前言:哇,这个我要么是没听过,要么是忘了,反正没印象。。。
2.4.1 原理&思想
1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序(我就直接复制了,这个写的挺好)
思想是使数组中任意间隔为h的元素都是有序的,这样的h数组被称为h有序数组。换句话说,一个有序数组就是h个相互独立的有序数组编织起来组成的一个数组。
(图肯定是偷(借用)的)
2.4.2 实现步骤
- 确定数组长度n,并决定分组长度n/2
- 进行分组的插入排序
- 在(n/2)/2上进行插入排序,在进行/2直到为1为止
2.4.3 复杂度
复杂度*:最佳情况:T(n) = O(nlog2 n) 最坏情况:T(n) = O(nlog2 n) 平均情况:T(n) =O(nlog2n)
2.4.4 代码
public class shell{
public static int[] ShellSort(int[] array) {
int len = array.length;
int temp, gap = len / 2;
while (gap > 0) {
for (int i = gap; i < len; i++) {
temp = array[i];
int preIndex = i - gap;
while (preIndex >= 0 && array[preIndex] > temp) {
array[preIndex + gap] = array[preIndex];
preIndex -= gap;
}
array[preIndex + gap] = temp;
}
gap /= 2;
}
return array;
}
}
2.4.4.1 对于代码的解释
- 首先,获取待排序数组的长度,并初始化一个变量 gap,表示步长,初始值为数组长度的一半。
- 进入循环,当步长大于0时进行排序。
- 在每一次排序中,从步长 gap 开始,遍历数组。
- 将当前位置的元素保存到临时变量 temp 中。
- 定义一个变量 preIndex,表示当前元素的前一个元素的索引位置(初始值为 i - gap)。
- 在内部循环中,判断 preIndex 的值是否大于等于0,且当前位置 array[preIndex] 的值是否大于 temp。如果满足条件,说明前一个元素比当前元素大,需要将前一个元素后移 gap 个位置。
- 将前一个元素后移 gap 个位置。
- 更新 preIndex 的值,继续向前查找。
- 将 temp 插入到正确的位置,即 preIndex + gap。
- 完成一次子序列的插入排序后,继续减小步长 gap。
- 重复步骤3-10,直到步长 gap 为1,即最后一次进行完整的插入排序。
- 返回排序后的数组。
实例*
假设我们有一个待排序的数组 [9, 5, 2, 7, 1]
。
- 初始时,步长 gap 为数组长度的一半,即 2。
- 第一次排序,按照步长 2 进行分组,得到 [9, 2] 和 [5, 7] 和 [1] 这三个子序列。
- 对每个子序列进行插入排序,得到 [2, 9] 和 [5, 7] 和 [1]。
- 此时,数组变为 [2, 5, 9, 7, 1]
- 第二次排序,步长 gap 减半,变为 1。
- 对整个数组进行插入排序,得到 [1, 2, 5, 7, 9]。
- 最终,数组排序完成。
2023年09月02日 21:08:02·
2.5 归并排序
分而治之
2.5.1 原理&思想
放个图先
(没错,图是借用的)
要将一个数组排序,可以先(递归的)将它分为两半分别排序,然后归并结合起来
优点:能够将任意长度为N的数组排序所需时间和
NlogN
成正比
缺点:所需的额外空间和N
成正比
2.5.2 复杂度
最佳情况:T(n) = O(n) 最差情况:T(n) = O(nlogn) 平均情况:T(n) = O(nlogn)
2.5.3 代码
public static int[] MergeSort(int[] array) {
if (array.length < 2) return array;
int mid = array.length / 2;
int[] left = Arrays.copyOfRange(array, 0, mid);
int[] right = Arrays.copyOfRange(array, mid, array.length);
return merge(MergeSort(left), MergeSort(right));
}
// 归并排序——将两段排序好的数组结合成一个排序数组
public static int[] merge(int[] left, int[] right) {
int[] result = new int[left.length + right.length];
for (int index = 0, i = 0, j = 0; index < result.length; index++) {
if (i >= left.length)
result[index] = right[j++];
else if (j >= right.length)
result[index] = left[i++];
else if (left[i] > right[j])
result[index] = right[j++];
else
result[index] = left[i++];
}
return result;
}
还没怎么看完,看书上还有原地归并,自顶向下的归并,自底向上的归并
顺便想到分治,把涉及到分治的提一嘴
二分搜索,大整数乘法,Strassen矩阵乘法,棋盘覆盖,合并排序,快速排序,线性时间选择,最接近点对问题,循环赛日程表,汉诺塔
(脖子有点不得劲,明天上课摸鱼写一写)
2023年09月04日 10:06:20
我觉得应该举个实例
假设输入的数组为[4, 2, 7, 1, 9, 5],首先调用MergeSort方法。
第一次递归:
array为[4, 2, 7],将其分成两个子数组left为[4],right为[2, 7]。
继续递归调用MergeSort方法,对left和right进行排序。
left为[4],直接返回。
right为[2, 7],继续递归调用MergeSort方法。
left为[2],直接返回。
right为[7],直接返回。
调用merge方法,将left和right合并为[2, 7],返回结果。
第二次递归:
array为[1, 9, 5],将其分成两个子数组left为[1],right为[9, 5]。
继续递归调用MergeSort方法,对left和right进行排序。
left为[1],直接返回。
right为[9, 5],继续递归调用MergeSort方法。
left为[9],直接返回。
right为[5],直接返回。
调用merge方法,将left和right合并为[5, 9],返回结果。
最后一次递归:
leftSorted为[2, 7],rightSorted为[5, 9]。
调用merge方法,将leftSorted和rightSorted合并为[2, 5, 7, 9],返回结果。
最终,MergeSort方法返回的排序数组为[1, 2, 4, 5, 7, 9]。
以下是一个简单的示例:
假设我们有两个已排序的子数组:
前一个数组:[1, 3, 5]
后一个数组:[2, 4, 6]
在合并时,我们将比较数组的第一个元素:
1 (来自前一个数组) < 2 (来自后一个数组)
因此,我们将 1 插入合并后的数组,并将前一个数组的下标向后移动一个位置:
合并后的数组:[1]
前一个数组:[3, 5]
后一个数组:[2, 4, 6]
我们继续比较数组的第一个元素:
3 (来自前一个数组) > 2 (来自后一个数组)
在这种情况下,我们交换这两个元素的位置:
合并后的数组:[1, 2]
前一个数组:[3, 5]
后一个数组:[3, 4, 6]
然后我们继续比较:
3 (来自前一个数组) < 3 (来自后一个数组)
相等的情况下,我们将元素按顺序插入合并后的数组,并将两个数组的下标都向后移动一个位置:
合并后的数组:[1, 2, 3]
前一个数组:[5]
后一个数组:[4, 6]
然后我们比较:
5 (来自前一个数组) < 4 (来自后一个数组)
合并后的数组:[1, 2, 3, 4]
前一个数组:[5]
后一个数组:[6]
比较:
5 (来自前一个数组) < 6 (来自后一个数组)
合并后的数组:[1, 2, 3, 4, 5]
前一个数组:[]
后一个数组:[6]
最后我们将剩下的元素直接插入合并后的数组中:
合并后的数组:[1, 2, 3, 4, 5, 6]
在每次合并时,我们始终比较两个数组的第一个元素,并根据大小进行合适的操作,以保持排序的正确性。
啊哈哈,举了两个,能理解就行,反正我是理解了~
哦哦,之后我还会在练习的时候把练习题的链接贴过来,nice
2.6 快速排序
2.6.1 原理
快速排序是一种分而治之的排序方法。它的工作原理是将数组分成两部分,然后独立地对部分进行排序。
快速排序很受欢迎,因为它不难实现,适用于各种不同类型的输入数据,并且比典型应用程序中的任何其他排序方法都要快得多。它是就地的(仅使用一个小的辅助堆栈),平均需要与 N log N 成比例的时间才能对 N 个项目进行排序,并且具有极短的内部循环。
上图!
该方法的关键是分区过程,该过程重新排列数组以使以下三个条件成立:
- 条目 a[j] 在数组中的最终位置,对于某些 j
- a[lo] 到 a[j-1] 中的任何条目都不大于 a[j]
- 在 a[j+1] 到 a[hi] 中,没有条目小于 a[j]
2.6.2 步骤
- 从数组中台哦出一个元素作为基准(pivot)
- 重排数组,以pivot为基准,小的在其前,大的在其后,此操作名为分区
- 递归地(recursive)把小于基准(pivot)元素的子数列和大于基准值元素的子数列排序。
2.6.3 复杂度
复杂度:
最佳情况:T(n) = O(nlogn) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(nlogn)
2.6.4 代码
/**
* 快速排序
* @param nums
*/
public static void quickSort(int[] nums) {
quickSort(nums,0,nums.length-1);
}
/**
* 快速排序
* @param nums
* @param low
* @param hight
*/
private static void quickSort(int[] nums, int low, int hight) {
if(low>hight){
return;
}
int i=low;
int j =hight;
int temp =nums[i];
while (i<j){
//先排右部分数组
while (nums[j]>=temp && i<j){
j--;
}
//比基准小的放左边
nums[i] = nums[j];
//后排左部分数组
while (nums[i]<=temp && i<j){
i++;
}
//比基准大的放右边
nums[j] = nums[i];
}
nums[i] = temp;
quickSort (nums,low,j-1);
quickSort (nums,j+1,hight);
}
快下课了,等下午再仔细研究研究,顺便做点题
啊·~~~,下午好困。。。。。想摆,等我再想想
2023年09月07日 15:01:09
贴个快排的c++模板
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l + r >> 1];
while (i < j)
{
do i ++ ; while (q[i] < x);
do j -- ; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j), quick_sort(q, j + 1, r);
}
2.6.5 hoare分区
2023年09月12日 09:19:08
先看了看代码,然后越看越不对劲,我就想,快排不是找privot嘛?这个算法怎么没有嘞?然后去了解了一波,原来这个东西叫Hoare分区算法,接下来我们在了解了解这个算法
为方便理解,我们举个栗子:假设我们有随机数组:[7, 2, 1, 6, 8, 5, 3, 4]
首先,选择数组的第一个元素 7 作为 pivot。然后,设置两个指针 i 和 j 分别指向数组的左边和右边:
i j
↓ ↓
[7, 2, 1, 6, 8, 5, 3, 4]
接下来,j 开始向左移动,直到找到一个小于等于 pivot 的元素:
i j
↓ ↓
[7, 2, 1, 6, 8, 5, 3, 4]
此时,j 停在了元素 3 上。然后,i 开始向右移动,直到找到一个大于等于 pivot 的元素:
i j
↓ ↓
[7, 2, 1, 6, 8, 5, 3, 4]
此时,i 停在了元素 6 上。接着交换 i 和 j 指向的元素:
i j
↓ ↓
[7, 2, 1, 3, 8, 5, 6, 4]
然后,j 再次向左移动,i 向右移动,继续找到需要交换的元素:
i j
↓ ↓
[7, 2, 1, 3, 8, 5, 6, 4]
再次进行交换:
i j
↓ ↓
[7, 2, 1, 3, 4, 5, 6, 8]
重复上述步骤,直到 i 和 j 相遇。在这个例子中,相遇的位置是 i = j = 4。此时,我们将 pivot 移动到相遇位置的地方:
i/j
↓
[4, 2, 1, 3, 7, 5, 6, 8]
数组被 pivot 分成了两部分,左边的元素都小于等于 pivot,右边的元素都大于等于 pivot。
接下来,对左右两个子序列分别使用同样的 Hoare 分区算法进行递归排序。这里只展示左边的子序列的排序过程,右边的子序列同理:
i j
↓ ↓
[1, 2, 3, 4, 7, 5, 6, 8]
继续递归排序左边的子序列,直到排序完成。最终得到排序后的结果为:
[1, 2, 3, 4, 5, 6, 7, 8]
这样就完成了 Hoare 分区算法的示例演示。通过将数组分成两个部分并使用递归,我们可以在每个子序列上重复这个过程,从而实现整个数组的排序。
仔细看了看,上面打的不太好,再来个图方便理解*
好吧。。。。有这个图我也没怎么看懂,到时候再研究研究。。。。
接下来到哪个算法了?
2.7 堆排序
2.7.1 原理
- 堆结构就是用数组实现的完全二叉树结构
- 完全二叉树中如果每棵子树的最大值都在顶部就是大根堆
- 反之为小根堆
- 堆结构的heapinsert与heapify操作
- heapinsert:新进入的元素都要去跟自己的父元素比较,如果大,就交换。时间复杂度和高度一致,O(logN)
- heapify:取出最大值时,将最后一个元素放到根节点,然后将heapSize-1,将父节点与左右孩子比较,大的放在父节点,然后周而复始。时间复杂度和高度一致,O(logN)
- 如果改变了堆中的一个值,先heapinsert然后heapify无脑完事
- 堆结构的增大与减小
- 优先级队列结构就是堆结构
2.7.1.1 堆结构的位置怎么找
名称 | 计算方法 |
---|---|
左孩子 | 2*i+1 |
右孩子 | 2*i+2 |
父节点 | (i-1)*2 |
2.7.2 基本思想&步骤
利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。
- 将待排序的序列构造成一个最大堆,此时序列的最大值为根节点
- 依次将根节点与待排序序列的最后一个元素交换
- 再维护从根节点到该元素的前一个节点为最大堆,如此往复,最终得到一个递增序列
才发现忘记上图
2.7.2.1 大顶堆与小顶堆
如他的名字一样
大顶堆就是大的在上,小顶堆就是小的在上
2.7.3 复杂度
时间复杂度:最佳情况:T(n) = O(nlogn) 最差情况:T(n) = O(nlogn) 平均情况:T(n) = O(nlogn)
2.7.4 代码
//声明全局变量,用于记录数组array的长度;
static int len;
public static int[] HeapSort(int[] array) {
len = array.length;
if (len < 1) return array;
//1.构建一个最大堆
buildMaxHeap(array);
//2.循环将堆首位(最大值)与末位交换,然后在重新调整最大堆
while (len > 0) {
swap(array, 0, len - 1);
len--;
adjustHeap(array, 0);
}
return array;
}
public static void buildMaxHeap(int[] array) {
//从最后一个非叶子节点开始向上构造最大堆
for (int i = (len - 1) / 2; i >= 0; i--) {
adjustHeap(array, i);
}
}
public static void adjustHeap(int[] array, int i) {
int maxIndex = i;
//如果有左子树,且左子树大于父节点,则将最大指针指向左子树
if (i * 2+1 < len && array[i * 2+1] > array[maxIndex])
maxIndex = i * 2+1;
//如果有右子树,且右子树大于父节点,则将最大指针指向右子树
if (i * 2 + 2 < len && array[i * 2 + 2] > array[maxIndex])
maxIndex = i * 2 + 2;
//如果父节点不是最大值,则将父节点与最大值交换,并且递归调整与父节点交换的位置。
if (maxIndex != i) {
swap(array, maxIndex, i);
adjustHeap(array, maxIndex);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)