排序算法 [Lv.1] (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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值