常用排序算法

冒泡排序

它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素已经排序完成。

这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

#冒泡排序的Swift示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func bubbleSort(_ nums: inout [Int]) {
let n = nums.count
for i in 0 ..< n-1 {//循环n-1次
for j in 0 ..< (n - 1 - i) {
if nums[j] > nums[j + 1] {
nums.swapAt(j, j + 1)//升序排列
}
}
}
print(nums)//打印结果
}
//调用示例
var nums = [5,8,1,4,2,7,6,3]
bubbleSort(&nums)

鸡尾酒排序

鸡尾酒排序也就是定向冒泡排序,是冒泡排序的一种变形。此演算法与冒泡排序的不同处在于排序时是以双向在序列中进行排序。

  1. 前半轮,从前往后比较相邻两个元素,将最大元素移到最后;
  2. 后半轮,从已排好序元素的前一位向前,比较相邻元素,将最小元素移到最前;
  3. 反复执行1.2两步,直到所有元素都排序完。

#鸡尾酒排序的Swift语言示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func cocktailSort(sourceArr array: inout [Int]) -> Void
{
let n = array.count
var left = 0
var right = n - 1

while left < right {

//前半轮 将最大元素移到最后
for i in left ..< right {
if array[i] > array[i+1]{
array.swapAt(i, i+1)
}
}
right -= 1

//后半轮 将最小元素移到最前
if left < right {
for i in (left+1 ... right).reversed() {//从后向前遍历
if array[i-1] > array[i]{
array.swapAt(i-1, i)
}
}
left += 1
}
}
}

选择排序

选择排序也是一种简单直观的排序算法。它的工作原理是在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

#选择排序的Swift语言示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func selectSort(sourceArr array: inout [Int]) -> Void
{
let n = array.count

for i in 0 ..< n-1 {// i为已排序序列的末尾
var min = i
for j in i+1 ..< n {// 未排序序列
if array[j] < array[min] {// 找出未排序序列中的最小值
min = j
}
}
if min != i {
array.swapAt(min, i)// 放到已排序序列的末尾,该操作很有可能把稳定性打乱,所以选择排序是不稳定的排序算法
}
}
}

插入排序

插入排序是一种简单直观的排序算法。它的工作原理非常类似于抓扑克牌,对于未排序数据(右手抓到的牌),在已排序序列(左手已经排好序的手牌)中从后向前扫描,找到相应位置并插入。

插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2~5

#插入排序的C语言示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void InsertionSort(int A[], int n)
{
for (int i = 1; i < n; i++)
{
int get = A[i]; // 右手抓到一张扑克牌
int j = i - 1; // 拿在左手上的牌总是排序好的
while (j >= 0 && A[j] > get) //将右手抓到的牌与左手手牌从右向左进行比较
{
A[j + 1] = A[j]; //如果该左手手牌比右手抓到的牌大,就将其右移
j--;
}
A[j + 1] = get; //直到该手牌比右手抓到的牌小(或相等),将右手抓到的牌插入到该手牌右边
}
}

int main()
{
int A[] = { 6, 5, 3, 1, 8, 7, 2, 4 };// 从小到大插入排序
int n = sizeof(A) / sizeof(int);
InsertionSort(A, n);

return 0;
}

快速排序算法

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。

一趟快速排序的算法是:

  1. 设置两个变量i、j,排序开始的时候:i=0,j=N-1;
  2. 以第一个数组元素作为关键数据,赋值给key,即key=A[0];
  3. 从j开始向前搜索,即由后开始向前搜索(j–),找到第一个小于key的值A[j],将A[j]和A[i]互换;
  4. 从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
  5. 重复第3、4步,直到i=j;
  6. 然后,对key两边的数据,再分组分别进行上述的过程,直到不能再分组为止。

#快速排序的C语言示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void sort(int *a, int left, int right)
{
if(left >= right){ //如果左边索引大于或者等于右边的索引就代表已经整理完成一个组了
return ;
}
int i = left;
int j = right;
int key = a[left];//1\2.默认取最前边的值

while(i < j) //5.在当组内寻找
{
//3.从后向前找第一个小于key的数
while(i < j && a[j] >= key)
{
j--;//向前寻找
}
a[i] = a[j];//找到一个这样的数后就把它赋给前面的被拿走的i的值

//4.从前向后找第一个大于key的数
while(i < j && a[i] <= key)
{
i++;//向后寻找
}
a[j] = a[i];
}
a[i] = key;//当在当组内找完一遍以后就把中间数key回归

//最后用同样的方式对分出来的左边的小组进行同上的做法
sort(a, left, i - 1);
//用同样的方式对分出来的右边的小组进行同上的做法
sort(a, i + 1, right);
//最后可能会出现很多分左右,直到每一组的 i = j 为止。

按照上面的方法,对数组 {49,38,65,97,76,13,27} 进行快速排序:

  • 进行一次快速排序之后,数组被划分为 {27,38,13} 49 {76,97,65}前后两部分。49之前的所有数都比它小,49之后的所有数都比它大;
  • 分别对前后两部分进行快速排序:{27,38,13} 经第三步和第四步交换后变成 {13,27,38} 完成排序。
  • {76,97,65} 经第三步和第四步交换后变成 {65,76,97} 完成排序。
  • 最终排序结果为:{13,27,38,65,76,97}。

相关参考:

#©SteveWang-常用排序算法总结(一)


常用排序算法
https://davidlii.cn/2018/10/09/algorithm.html
作者
Davidli
发布于
2018年10月9日
许可协议