【算法】选择排序和插入排序

1. 前言

本文是基于本人在《算法(第4版)》的学习过程中做的笔记

接下来会介绍两种简单的初级排序算法:

  • 选择排序
  • 插入排序

首先是接下来要学习的排序算法的一套通用模板

/**
	排序算法模板
**/
public class Example {
	public static void sort(Comparable[] a) {//具体算法见下文
	}
	
	
	private static boolean less(Comparable v, Comparable w) {
		return v.compareTo(w) < 0;
	}
	
	private static void exch(Comparable[] a, int i, int j) {
		Comparable t = a[i];
		a[i] = a[j];
		a[j] = t;
	}
	
	private static void show(Comparable[] a) {
		for(int i=0; i<a.length; i++)
			StdOut.print(a[i] + " ");
		StdOut.println();
	}
	
	public static boolean isSorted(Comparable[] a) {
		for(int i=1; i<a.length; i++) {
			if(less(a[i], a[i-1]))	//默认升序排列
				return false;
		}
		return true;
	}
}

2. 选择排序

2.1 简述

选择排序是一种非常简单直观的排序算法,它的基本思想是:

  • 在当前的比较轮数 i 中,找到从数组下标 i+1 到 数组末尾的最小值,并与 当前的元素 a[i]进行比较,若小于当前元素a[i],则进行交换

也就是说在每一轮比较的时候,我们要做的就是找到当前元素及后所有元素的最小值,并将该最小值放入正确的位置

2.2 实现

	/**
	选择排序核心代码
	*/
	public static void sort(Comparable[] a) {
   	int N = a.length;
   	
   	for(int i=0; i<N; i++) { //升序排列
   		int min = i;
   		for(int j=i+1; j<N; j++) {
   			if(less(a[j], a[min]))	//每次比较两个元素大小,找到当前的最小元素
   				min = j;
   		}
   		exch(a, i, min);
   	}
   }

从代码中我们可以很自然地体会当中的思想,接下来我们看下选择排序的轨迹,更加直观地观察它是怎么运作的:
在这里插入图片描述
在这里插入图片描述
根据运行轨迹我们可以发现:选择排序运行的过程中,不会去访问当前索引左侧的元素

2.3 分析

显然可以看出,在有n个元素选择排序的最坏情况下:
我们总共需要n次的交换(n次的外层循环中,每次都要交换),以及大约 n 2 / 2 n^2/2 n2/2 次的比较(最坏情况下,第一轮需要n-1次比较,第二轮需要n-2次比较,…,第n-1轮需要1次比较,而最后一轮不执行内层循环)

2.4 小结

总的来说,选择排序的思想便于理解,实现方便,并且它还有两个很鲜明的特点

  1. 运行时间与输入无关。不管如何,选择排序都会去扫描一边数组找出最小的元素,当使用选择排序的人可能会吃惊于:相同长度的有序数组和随机数组的排序时间一样!
  2. 较少的数据交换。每一个外层循环我们才会去交换一次数据,总长度为n的数组总共就交换了n次,因此选择排序中数组交换所带来的开销是较小的

3. 插入排序

3.1 简述

插入排序的思想其实很简单:

  • 想象我们在玩扑克牌的过程中,对于一副抓到的手牌会进行理牌,而插入排序就是对这个理牌的过程的模拟:将第 j 张牌顺到前面合适的位置

例如:我们抓到了一手牌——5 、9 、8 、K 、7 、3

  1. 首先,我们很自然地会把8先挪到9的前面,这时牌序为5 、8 、9 、K 、7 、3
  2. 然后,将7在插入8的前面,这时为5 、7 、8 、9 、K 、3
  3. 最后,将A插入到最左端,得到了正确的牌序:3 、5 、7 、8 、9 、K

3.2 实现

/**
插入排序核心代码
*/
	public static void sort(Comparable[] a) {
		int N = a.length;

		for (int i = 1; i < N; i++) {
			for (int j = i; j > 0 && less(a[i], a[j - 1]); j--) { // 将a[j]置入前面序列中合适的位置
				exch(a, j, j - 1);
			}
		}
	}

我们再来观察一下插入排序的运行轨迹:
在这里插入图片描述
根据运行轨迹我们可以发现:插入排序运行的过程中,不会去访问当前索引右侧的元素

3.3 分析

我们先从最好情况开始分析:

  • 在长度为n的数组中,当所有的元素都已升序排列后,插入排序便无需进行交换操作,仅仅需要n-1次的比较(外层循环执行了n-1次)

接下来我们看看最坏的情况

  • 所有元素都逆序排列时,第一轮需要1次比较、1次交换;第二轮需要2次比较、两次交换,…,第n-1轮需要n-1次比较、n-1次交换

3.4 小结

总的来说,插入排序对于部分有序的数组十分高效,也很适合小规模的数组

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值