总结下常见的几种排序及其实现,帮助自己加深记忆。
一、冒泡排序
1、原理: 通过依次比较相邻的元素,将较大(或较小)的元素交换到右侧,直到整个序列有序。
csharp
public static int[] BuddleSort(int[] arry)
{
int n= arry.Length;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n - 1 - i; j++)
{
if (arry[j] < arry[j + 1])
{
int temp = arry[j];
arry[j] = arry[j + 1];
arry[j + 1]= temp;
}
}
}
return arry;
}
2、算法步骤:
冒泡排序的基本步骤:
(1)遍历数组: 从第一个元素开始,依次比较相邻的两个元素。
(2)比较相邻元素: 比较当前元素和下一个元素的大小关系。
(3)交换位置: 如果当前元素大于下一个元素(升序排序),则交换它们的位置,否则不做任何操作。
(4)遍历次数: 完成一轮遍历后,最大(或最小)的元素就会沉到数组的最后一个位置。
(5)重复步骤: 重复执行以上步骤,直到数组中的所有元素都已经排序完成。
3、举例:
冒泡排序的每个步骤涉及多次比较和可能的交换操作。这里是对数组 [65, 24, 12, 32, 4, 15] 进行冒泡排序时的每个步骤的输出:
初始数组:[65, 24, 12, 32, 4, 15]
第1次内循环执行后:[24, 12, 32, 4, 15, 65]
第2次内循环执行后:[12, 24, 4, 15, 32, 65]
第3次内循环执行后:[12, 4, 15, 24, 32, 65]
第4次内循环执行后:[4, 12, 15, 24, 32, 65]
第5次内循环执行后:[4, 12, 15, 24, 32, 65]
因此,经过选择排序算法的执行后,数组变为 [4, 12, 15, 24, 32, 65]。
4、时间复杂度
冒泡排序的时间复杂度为 O(n^2)。
二、选择排序
1、原理: 每次从未排序的部分选择最小(或最大)的元素,放到已排序部分的末尾,直到整个序列有序。
csharp
public static int[] SelectSort(int[] arry)
{
int n = arry.Length;
int temp;
for (int i = 0; i < n - 1; i++)
{
int minIndex = i;
for (int j = i+1; j < n - 1 ; j++)
{
if(arry[j]<arry[minIndex])
minIndex = j;
}
temp = arry[i];
arry[i] = arry[minIndex];
arry[minIndex] = temp;
}
return arry;
}
2、算法步骤:
首先,找到数组中最小的元素,并将其与数组的第一个元素交换位置。
接下来,在剩余的未排序部分中找到最小的元素,并将其与数组的第二个元素交换位置。
以此类推,直到所有元素都被排序。
3、举例
比如我有个数组[65, 24, 12, 32, 4, 15],使用选择排序每次内循环执行后的输出内容如下:
初始状态:[65, 24, 12, 32, 4, 15]
第1次内循环执行后:[4, 24, 12, 32, 65, 15]
第2次内循环执行后:[4, 12, 24, 32, 65, 15]
第3次内循环执行后:[4, 12, 15, 32, 65, 24]
第4次内循环执行后:[4, 12, 15, 24, 65, 32]
第5次内循环执行后:[4, 12, 15, 24, 32, 65]
因此,经过选择排序算法的执行后,数组变为 [4, 12, 15, 24, 32, 65]。
4、时间复杂度
选择排序的时间复杂度为 O(n^2)。
三、插入排序
1、原理: 将数组分为已排序和未排序两部分,每次从未排序部分取出一个元素,插入到已排序部分的合适位置。
方法一:
csharp
public static int[] InsertSort(int[] arry)
{
int n=arry.Length;
for (int i = 1; i < n; i++)
{
int key = arry[i];
int j = i - 1;
while (j >= 0 && key > arry[j])
{
arry[j+1]=arry[j];
j--;
}
arry[j+ 1] = key;
}
return arry;
}
插入排序方法二,我把方法一中内循环结束把key赋值的地方修改到内循环中了,感觉这种更好理解,类似于交换数据,但可能效率没有方法一好。
csharp
public static int[] InsertSort2(int[] arry)
{
int n = arry.Length;
for (int i = 1; i < n; i++)
{
int key = arry[i];
int j = i - 1;
while (j >= 0 && key > arry[j])
{
arry[j+1] = arry[j];
arry[j]= key;
j--;
}
}
return arry;
}
2、算法步骤:
(1)从第一个元素开始,将该元素视为已排序部分。
(2)取出下一个元素,在已排序部分从后往前扫描。
(3)如果已排序部分的元素大于新元素,将该元素向右移动一个位置。
(4)重复步骤3,直到找到已排序部分的元素小于或等于新元素的位置。
(5)将新元素插入到找到的位置。
(6)重复步骤2到步骤5,直到所有元素都被插入到已排序部分。
3、举例
比如我有个数组[65, 24, 12, 32, 4, 15],使用插入排序每次内循环执行后的输出内容如下:
初始序列:[65, 24, 12, 32, 4, 15]
第一次循环后:[24, 65, 12, 32, 4, 15]
第二次循环后:[12, 24, 65, 32, 4, 15]
第三次循环后:[12, 24, 32, 65, 4, 15]
第四次循环后:[4, 12, 24, 32, 65, 15]
第五次循环后:[4, 12, 15, 24, 32, 65]
最终排序完成的序列为:[4, 12, 15, 24, 32, 65]
四、快速排序
1、原理
选择一个基准元素,将数组分成两部分,左边的部分小于基准,右边的部分大于基准,然后对左右两部分递归进行排序,直到整个数组有序。
csharp
public static int[] QuickSort(int[] arry,int low, int high)
{
if (low < high)
{
int pivot =Partition(arry,low,high);
QuickSort(arry,low,pivot-1);
QuickSort(arry, pivot+1, high);
}
return arry;
}
public static int Partition(int[] arry, int low, int high)
{
int pivot = arry[low]; // 将第一个元素作为基准元素
while (low < high)
{
// 从右向左找到一个小于基准元素的值
while (low < high && arry[high] >= pivot)
{
high--;
}
// 将这个小于基准元素的值放到左侧
arry[low] = arry[high];
// 从左向右找到一个大于基准元素的值
while (low < high && arry[low] <= pivot)
{
low++;
}
// 将这个大于基准元素的值放到右侧
arry[high] = arry[low];
}
// 将基准元素放到正确的位置上
arry[low] = pivot;
// 返回基准元素的索引
return low;
}
2、算法步骤:
(1)选择基准元素 : 从数组中选择一个基准元素,通常是数组的第一个元素、最后一个元素或者中间的元素。
(2)分区操作 : 将数组中的元素按照基准元素的大小分成两部分,小于基准元素的放在基准元素的左边,大于基准元素的放在右边。分区操作完成后,基准元素的位置就确定了。
Partition函数中的While循环解释如下:选择arry[0]作为基准元素。
- 双指针移动: 使用两个指针分别指向数组的起始位置和结束位置。左指针从左向右移动,直到找到一个大于或等于基准元素的值;右指针从右向左移动,直到找到一个小于或等于基准元素的值。
- 交换元素: 一旦找到了左侧大于基准元素和右侧小于基准元素的值对,就交换它们的位置。这样,就保证了左侧的元素都小于等于基准元素,右侧的元素都大于等于基准元素。
- 重复操作: 继续移动双指针,直到它们相遇为止。此时,所有小于基准元素的值都在基准元素的左侧,所有大于基准元素的值都在基准元素的右侧。
- 基准元素归位: 将基准元素放置在正确的位置上,一般是与相遇点进行交换。
(3)递归排序 : 递归地对基准元素左右两边的子数组进行快速排序,直到子数组的大小为 0 或 1,即已经有序。
(4)合并结果 : 当所有的子数组都有序时,整个数组也就有序了。
说明:网上有很多视频可以很好的解释这个算法的排序规则,可以在B站搜索看下。
3、时间复杂度
快速排序的平均时间复杂度是O(nlogn),但是在实际排序中,时间复杂度和基准元素(枢轴)的选择有关。如果枢轴选取不好,那么快速排序有可能就会退化为冒泡排序,时间复杂度为O(n*n)。