排序算法 [Lv.1]

Posted VermillionTear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了排序算法 [Lv.1] 相关的知识,希望对你有一定的参考价值。

摘要

本文主要归纳整理常见排序算法的原理以及实现,对部分内容进行适度展开。

环境

  • Google Chrome 91.0.4472.114(64 位)

资源

准备工作

  1. 以下算法的讲解以及实现部分,均以下面的数组作为初始数组。
1123046818
  1. 算法的测试采用html内嵌JS代码的方式,HTML均使用以下代码作为模板,
    algorithm.html
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>algorithm</title></head>
<body>
<script>

// 打印数组。
function print_array(arr) {
	for (let key in arr){
		if (typeof(arr[key]) == 'array' || typeof(arr[key]) == 'object') {
			print_array(arr[key])
		} else {
			document.write(key + ' = ' + arr[key] + '<br>')
		}
	}
}

// TODO,算法实现。

let arr = new Array(11, 23, 0, 46, 8, 18)
print_array(arr)
document.write('<br>')

// TODO,调用算法。

document.write('<br>')
print_array(arr)

</script>
</body>
</html>

正式开始

冒泡排序

原理

  1. len范围(len初始为数组的长度)内,依次比较相邻的元素,如果前一个比后一个大,则交换他们两个。
  2. len每次减1
  3. 重复步骤1~2,直到len1

简单直观的排序算法,每次的步骤1都保证了最大的元素放到了数组的末端。
我倒觉得这种排序算法更像是最大元素不断沉底的过程,叫沉底排序更为贴切。
时间复杂度: O ( n 2 ) O(n^{2}) O(n2)

步骤

  1. len初始为数组的长度。
{–len范围––len范围––len范围––len范围––len范围––len范围–}
1123046818
  1. len范围内,依次比较相邻的元素,如果前一个比后一个大,则交换他们两个。
{–len范围––len范围––len范围––len范围––len范围––len范围–}
1102346818
{–len范围––len范围––len范围––len范围––len范围––len范围–}
1102384618
{–len范围––len范围––len范围––len范围––len范围––len范围–}
1102381846
  1. len每次减1
{–len范围––len范围––len范围––len范围––len范围–}
1102381846
  1. 重复步骤2~3,直到len1

实现

function bubbleSort(arr) {
    for (let i = 0; i < arr.length - 1; i++) {
        let len = (arr.length - 1) - i      // i在不断+1,len在每次循环就不断-1。

        for (let j = 0; j < len; j++) {
            if (arr[j] > arr[j + 1]) {    // 依次比较相邻的两个元素。
                // 如果前一个比后一个大,则交换他们两个。
                let temp = arr[j + 1]
                arr[j + 1] = arr[j]
                arr[j] = temp
            }
        }
    }
}

bubbleSort(arr)

选择排序

原理

  1. 将整个数组视为未排序序列,找到其中的最小值。
  2. 将找到的最小值交换到数组的起始位置,并将其视为已排序序列。
  3. 在剩余未排序序列中找到最小值,并放到已排序序列的末尾。
  4. 不断重复步骤3,直到所有未排序序列中的元素,均已放到已排序序列中。

简单直观的排序算法,每次从数组中取出一个最小值,依次按顺序排好。
时间复杂度: O ( n 2 ) O(n^{2}) O(n2)

步骤

  1. 将整个数组视为未排序序列,找到其中的最小值。
{–未排序序列––未排序序列––未排序序列––未排序序列––未排序序列––未排序序列–}
1123046818
最小值
  1. 将找到的最小值交换到数组的起始位置,并将其视为已排序序列。
{–已排序序列–}{–未排序序列––未排序序列––未排序序列––未排序序列––未排序序列–}
0231146818
  1. 在剩余未排序序列中找到最小值。
{–已排序序列–}{–未排序序列––未排序序列––未排序序列––未排序序列––未排序序列–}
0231146818
最小值
  1. 将找到的最小值交换到已排序序列的末尾。
{–已排序序列––已排序序列–}{–未排序序列––未排序序列––未排序序列––未排序序列–}
0811462318
  1. 重复步骤3~4,直到所有未排序序列中的元素,均已放到已排序序列中。

实现

function selectionSort(arr) {
    let len = arr.length

    // 这里做了一点优化,当未排序序列仅剩一个值时,这个值必然>=已排序序列中的所有值
    // (如果不是这样的话,这个值不会被交换到最后)
    // 所以最后这一个值没有必要再进行排序。
    for (let i = 0; i < len - 1; i++) {
        let minIndex = i

        for (let j = i + 1; j < len; j++) {     // 已排序序列末尾元素之后,就是未排序序列的开始。
            if (arr[j] < arr[minIndex]) {
                minIndex = j    // 找到最小值的索引。
            }
        }

        // 交换到已排序序列的末尾。
        let temp = arr[i]
        arr[i] = arr[minIndex]
        arr[minIndex] = temp
    }
}

selectionSort(arr)

直接插入排序

原理

  1. 将数组的第一个元素看做一个已排序好的有序表。
  2. 将有序表的下一个元素作为待插入元素,用tmp记录。
  3. 从有序表的最后一个元素开始,向前寻找插入位置,比tmp大的元素都要向后移动(插入位置是有序表中第一个比tmp小的元素之后,或是有序表的起始位置)。插入后有序表的长度加1,并且依然有序(有序表中,插入位置右边的元素都比待插入元素大,左边的元素都比带插入元素小)。
  4. 重复步骤2~3,直到没有待插入的元素,整个数组排序完成。

每次步骤2~3都在不断扩充有序表。
就像玩扑克牌的时候,我们每抽到一张牌,都是将它插入到当前手牌中的合适位置。
在这里插入图片描述
时间复杂度: O ( n 2 ) O(n^{2}) O(n2)

步骤

  1. 将数组的第一个元素看做一个已排序好的有序表。将有序表的下一个元素作为待插入元素,用tmp记录(tmp = 23)。
{–有序表–}待插入元素
1123046818
  1. 从有序表的最后一个元素开始,向前寻找插入位置,比tmp大的元素都要向后移动。
    (此次比较,由于有序表中最后一个元素就比tmp小,所以没有向后移动的操作)
{–有序表–}
1123046818
插入位置
  1. 在插入位置,放入tmp。有序表的长度加1,并且依然有序。
{–有序表––有序表–}
1123046818
插入位置
  1. 将有序表的下一个元素作为待插入元素,用tmp记录(tmp = 0)。
{–有序表––有序表–}待插入元素
1123046818
  1. 从有序表的最后一个元素开始,向前寻找插入位置,比tmp大的元素都要向后移动
    (此次比较,由于有序表中的元素都比tmp大,所以插入位置就是有序表的起始位置)。
{–有序表––有序表–}
11112346818
插入位置
  1. 在插入位置,放入tmp。有序表的长度加1,并且依然有序。
{–有序表––有序表––有序表–}
0112346818
插入位置
  1. 重复步骤4~6,直到没有待插入元素。

实现

function insertSort(arr){
	for (let i = 1; i < arr.length; i++) {	// 从数组第二个元素开始作为待插入元素。
		let tmp = arr[i]	// 待插入元素。
		let j = i - 1	// 初始为有序表最后一个元素的索引。
		
		while (j >= 0 && arr[j] > tmp) {	// 从有序表最后一个元素开始,向前寻找插入位置。
			arr[j + 1] = arr[j]		// 比tmp大的元素都要向后移动
			j--		// 向前寻找。
		}
		arr[j + 1] = tmp	// 找到插入位置,存入tmp。
	}
}

insertSort(arr)

快速排序

原理

  1. 找一个基准值tmp
  2. 把数组中比tmp小的都放在tmp的左边。
  3. 把数组中比tmp大的都放在tmp的右边。
  4. tmp左边的数据看成一个“数组”,把tmp右边的数据看成一个“数组”,分别重复步骤1~4
  5. 直到左右“数组”都剩下1个元素,整个数组排序完成。

每次步骤1~4都实现了数组元素以基准值tmp左右划分,左边的小,右边的大。
其实,同时也是找到了基准值tmp在数组中的正确位置。
时间复杂度: O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

步骤

  1. low记录数组起始的索引,high记录数组结束的索引,以数组的第一个元素作为基准值,并用tmp存储(tmp = 11)。现在low的位置相当于空出来了,要找一个比tmp小的值放在这里。
low
1123046818
high
  1. high的位置开始不断向前找,找到一个比tmp小的值 (8 < 11)
low
1123046818
high
  1. low的位置存储high找到的值,
    现在相当于high的位置又空出来了,要找一个比tmp大的值放在这里。
low
823046818
high
  1. low的位置开始不断向后找,找到一个比tmp大的值 (23 > 11)
low
823046818
high
  1. high的位置存储low找到的值
low
8230462318
high
  1. 重复步骤2~5,接下来几步的变化
low
8230462318
high
low
800462318
high
low
800462318
high
  1. lowhigh在同一个位置时结束寻找,此时将tmp的值放在此处,
    以下这个过程,实现了tmp左边的都比其小,右边的都比其大。
low
8011462318
high
  1. 再将tmp的左右分别看成“数组”,重复步骤1~8,直到左右看做的“数组”都只有1个或0个元素。
    tmp左边看做数组,
low
80
high

tmp右边看做数组,

low
462318
high

实现

function quickSort(arr, low, high) {
	if (low < high) {
		let index = getIndex(arr, low, high)	// 找到基准值在数组中的正确位置。

		quickSort(arr, low, index - 1)	// 将tmp左边看成一个“数组”。
		quickSort(arr, index + 1, high)	// 将tmp右边看成一个“数组”。
	}
	// 直到看做的“数组”都只有1个或0个元素(low >= high),则直接返回。
}

function getIndex(arr, low, high) {
	let tmp = arr[low]		// low的位置作为基准值,用tmp存储。
	while (low < high) {
		// 先让high开始找,不断向前,找到一个比tmp小的值。
		// (或者没找到,high走到了low的位置,也结束循环)
		while (low < high && arr[high] >= tmp) {
			high--
		}
		arr[low] = arr[high];	// low的位置存储high找到的值。
		// 再让low开始找,不断向后,找到一个比tmp大的值。
		// (或者没找到,low走到了high的位置,也结束循环)
		while (low < high && arr[low] <= tmp) {
			low++
		}
		arr[high] = arr[low]	// high的位置存储low找到的值。

	}
	// 此时low与high在同一个位置,此处存储tmp的值。
	arr[low] = tmp
	return low		// 返回基准值tmp在数组中的正确索引。
}

// 初始,low是数组的第一个元素,high是数组的最后一个元素。
quickSort(arr, 0, arr.length - 1)

希尔排序

原理

  1. 找一个间隔gap
  2. 把数组按照gap分成多组。
  3. 对每个分组进行直接插入排序
  4. 缩小gap的取值,重复步骤2~4
  5. 直到gap的取值为0时,整个数组排序完成。

每次步骤2~4都实现了分组内的有序,分组不断变少,最后整个数组成为一组,对这一组进行直接插入排序,整个数组排序完成。
时间复杂度: O ( n 2 ) O(n^{2}) O(n2)

步骤

  1. gap初始为数组长度 / 2gap的值向下取整。
    gap == 3
group1group2group3group1group2group3
1123046818
  1. 对每组进行直接插入排序,
group1group2group3group1group2group3
1180462318
  1. 每次缩小gap都再次除以2
    gap == 1
group1group1group1group1group1group1
1180462318
  1. 重复步骤2~3,直到gap的取值为0时,整个数组排序完成。

实现

function shellSort(arr) {
	// gap不断缩小,直到为0时,整个数组排序完成。
	for (let gap = Math.floor(arr.length / 2); gap > 0; gap = Math.floor(gap /= 2)) {
		// for循环中都是直接插入排序的逻辑,需要注意的是,每组数组元素索引之间不再是相差1,二是相差gap。
		for (let i = gap; i < arr.length; i++) {		// 每组数组都从第二个元素开始作为待插入元素。
			let tmp = arr[i]
			let j = i - gap

			while (j >= 0 && arr[j] > tmp) {
				arr[j + gap] = arr[j]
				j -= gap
			}
			arr[j + gap] = tmp
		}
	}
}

shellSort(arr)

以上是关于排序算法 [Lv.1] 的主要内容,如果未能解决你的问题,请参考以下文章

排序算法 [Lv.1]

排序算法 [Lv.1]

Java排序算法 - 堆排序的代码

7种基本排序算法的Java实现

算法排序之堆排序

快速排序-递归实现