八大排序算法(Java代码)

1. 直接插入排序(Straight Insertion Sort)


  • 基本要点:将一个数据插入到排序好的有序列表中,从而得到一个长度加1的的有序表。

  • 直接插入实现原理:先将索引为0位置的值看成是一个有序的子序列,然后从第二个开始逐个进行插入操作,直到整个序列有序。

  • 总结:将第一个数作为一个有序列表,然后第二个和第一个比较,即:每次都与最后一位进行比较,若大于,直接插在后面,若小于,往前一位继续比较,重复操作,直到大于以后,插入在后面或小于第一位后,放在第一位位置上,后面的数据依次后移。

  • 时间复杂度O(n2)

  • 空间复杂度O(1)

  • 其他插入方法:二分法,2-路插入法

  • 代码实现

  /**
     * 直接插入排序法
     */
    public void straightInsertionSort(int[] array) {
    	// 从小到大排序
    	for (int i = 1; i < array.length; i++) {
    		// 若第i个元素大于i-1元素,直接插入(相当于位置不变)。
    		// 小于的话,移动有序表后插入(即依次交换位置)
    		if (array[i] < array[i - 1]) {
    			// 存储要交换位置的索引,即最后一个比他小的数的位置
    			int index = i - 1;
    			// 作为临时变量,存储要插入的数据(即待排序的数据)将a[i]位置空出来,用于移动
    			int temp = array[i];
    			// 将前一个数据后移,因为已经比前一个数据大了,所以直接移动一位
    			array[i] = array[i - 1];
    			// 依次前移比较,直到找到比temp大的数。获得第一个比他大的数的索引index
    			while (index != -1 && array[index] > temp) {
    				array[index + 1] = array[index];
    				// 元素后移,继续比较
    				index--;
    			}
    			// 插入到第一个比他大的数据后面->正确位置
    			array[index + 1] = temp;
    		}
    	}
    }

2. 希尔排序(Shell’s Sort)

基本思路:先将整个待排序的记录序列分割成若干个子序列,分别进行直接插入排序,待整个序列中的记录“基本有序”,再对全体记录进行依次直接插入排序。(若不进行最后这个操作,希尔排序算法会不稳定。=>最后的操作是将增量设置为1。)

:很多算法解析中的增量解析都很难理解,这里做一下自我理解的增量解释,欢迎指正。

增量:每次子序列相隔的长度。增量越小,越稳定。即:分组时,是以{a[i],a[i+增量],a[i+增量+增量],a[i+增量+…+增量]}为一组进行比较的,直到数组a的下标超出长度。则不再循环。等于1时,是相邻的进行比较。

时间复杂度:没有确切的说法,不完全说法为O(n1.3)

空间复杂度O(1)

代码示例

	/**
	 * 希尔排序(非稳定排序)
	 * 
	 * @param a 排序的数组
	 */
	public void heerSort(int[] a) {
		// 设置增量为数组的一半
		int d = a.length / 2;
		while (true) {
			for (int i = 0; i < d; i++) {
				// 第j个和第j+d进行比较
				for (int j = i; j + d < a.length; j += d) {
					// 进行位置交换
					if (a[j] > a[j + d]) {
						int temp = a[j];
						a[j] = a[j + d];
						a[j + d] = temp;
					}
				}
			}
			// 小于或等于1的增量,再继续运算没有意义 直接退出循环
			if (d <= 1) {
				break;
			}
			// 增量递减 比较范围缩短(越密集越准确)
			//可以为其他的,例如:d-=2;但密集度不够,可能会导致排序不够稳定。
			d--;
		}
	}

3. 简单选择排序(Simple Selection Sort)


基本思想:在要排序的一组数中,选出最小(或最大) 的一个数与第1个位置交换,然后在剩下的数当中再找最小(或最大)的数与第2个位置交换,依次类推,直到n-1(倒数第二个)元素和第n(最后一个元素)比较为止。

时间复杂度O(n2)

空间复杂度O(1)

代码实现:

	/**
	 * 选择排序
	 * 
	 * @param array
	 */
	public void selectSort(int[] array) {
		// 每一个都进行遍历,和后面的进行比较。
		for (int i = 0; i < array.length; i++) {
			for (int j = i; j < array.length; j++) {
				// 满足条件交换位置
				if (array[i] < array[j]) {
					int temp = array[i];
					array[i] = array[j];
					array[j] = temp;
				}
			}
		}
	}

4. 堆排序(Heap Sort)


基本思想:将数组下标构成一个大堆,即一维数组作为二叉树表示,再得到一个大堆。每构造一次大堆,该序列中的最大值就会沉淀(交换)到下标为0的地方。然后将下标为0的值和数组当前(每次交换后,大堆构造的长度-1)最后一位交换位置,继续进行大堆排序。直到序列有序。
利用二叉树的原理,将数组看作二叉树,进行大堆构造,将构造好的根节点(下标为0 的值)和最后一位交换,然后length-1,继续大堆构造。

大堆:根节点的值都比子节点的值大,则为大堆。
小堆:根节点的值都比子节点的值小,则为小堆。

时间复杂度O(nlog2n)

空间复杂度O(1)

代码示例

	/**
	 * 堆排序
	 * 
	 * @param a
	 */
	public void heapSort(int[] a) {
		// 构建大堆
		buildMaxHeap(a);
		for (int i = a.length - 1; i >= 1; i--) {
			// 最大元素已经排在了下标为0的地方,把它放在最后面。循环执行,达到从小到大排序
			exchangeElements(a, 0, i);// 每交换一次,就沉淀一个大元素
			// 每次都从顶端开始构造,然后取出来放在该长度的最后面,最后长度-1。继续重复操作
			maxHeap(a, i, 0);
		}
	}

	/**
	 * 构建大堆
	 * 
	 * @param a
	 */
	private void buildMaxHeap(int[] a) {
		// 假设长度为9
		// 只需要遍历一半 通过左右孩子分别等于2i+1 2i+2 可以直接得到他们的值,不用再进行遍历
		int half = (a.length - 1) / 2;
		for (int i = half; i >= 0; i--) {
			// 若长度为9,只需遍历43210
			maxHeap(a, a.length, i);
		}
	}

	/**
	 * 
	 * @param a      要遍历的数组
	 * @param length 表示用于构造大堆的数组的长度元素数量=>构造过以后,构造完的不再重复构造,即length-1
	 * @param i      表示从哪个节点遍历,由于大堆构造后会将值赋给最后一位,因此一般为0
	 */
	private void maxHeap(int[] a, int length, int i) {
		// 左节点序号
		int left = i * 2 + 1;
		// 右节点序号
		int right = i * 2 + 2;
		// 最大的节点序号
		int largest = i;
		// 左边节点的比根节点的值大,将最大节点的序号改为left
		if (left < length && a[left] > a[largest]) {
			largest = left;
		}
		// 右边节点的比根节点的值大,将最大节点的序号改为left
		if (right < length && a[right] > a[largest]) {
			largest = right;
		}
		// 如果largest不等于i,则表示左右节点中有一个比它大
		if (i != largest) {
			// 进行数据交换
			exchangeElements(a, i, largest);
			// largest进行交换后,以他为根节点,对下面的左右孩子进行大堆构造,避免a[i]小于它下面的左右孩子
			maxHeap(a, length, largest);
		}
	}

	/**
	 * 在数组a里进行两个下标元素交换
	 * 
	 * @param a       要交换的数组
	 * @param i       要交换的父节点
	 * @param largest 要交换的子节点
	 */
	private void exchangeElements(int[] a, int i, int largest) {
		int temp = a[i];
		a[i] = a[largest];
		a[largest] = temp;
	}

5. 冒泡排序(Bobble Sort)


基本思想:在要排序的一组数中,对当前还未排序的全部数,相邻的两个数依次进行比较和调整,让较大的数往上冒,较小的往下沉。即:每当两相邻的数比较后发现其排序与排序要求相反时,就将它们互换。

时间复杂度O(n2)

空间复杂度O(1)

代码示例
1.传统冒泡法:一次找出一个最大值或最小值。

	/**
	 * 冒泡排序
	 * 
	 * @param a
	 */
	public void bubbleSort(int[] a) {
		for (int i = 0; i < a.length - 1; i++) {
			for (int j = 0; j < a.length - 1 - i; j++) {
				if (a[j] > a[j + 1]) {
					int temp = a[j];
					a[j] = a[j + 1];
					a[j + 1] = temp;
				}
			}
		}
	}

2.双向冒泡法:一次找出一个最大值和最小值。然后两头同时缩减长度,让遍历次数减少。

	/**
	 * 双向冒泡排序
	 * 
	 * @param a
	 */
	public void bidirectionalBubbleSort(int[] a) {
		// 即将放置最小数位置
		int bottom = 0;
		// 即将放置最大数位置
		int top = a.length - 1;
		// 交换时暂存数据
		int tmp, j;
		// 当放置最小值的位置大于等于放置最大值位置时,意味着两边的排序都完成了
		while (bottom < top) {
			// 正向冒泡,找到最大值
			for (j = bottom; j < top; ++j)
				if (a[j] > a[j + 1]) {
					tmp = a[j];
					a[j] = a[j + 1];
					a[j + 1] = tmp;
				}
			// 修改即将放置最大数位置, 前移一位
			top--;
			// 反向冒泡,找到最小值
			for (j = top; j > bottom; --j)
				if (a[j] < a[j - 1]) {
					tmp = a[j];
					a[j] = a[j - 1];
					a[j - 1] = tmp;
				}
			// 修改即将放置最小数位置,后移一位
			bottom++;
		}
	}

6. 快速排序(Quick Sort)


基本思想

  1. 选择一个基数(基准元素),通常为第一个元素或者最后一个元素。
  2. 通过一次排序后将待排序的记录分割成独立的两部分,其中一部分比基数大,一部分比基数小。(排序过程中,将比基数大和小的数位置做了调整)
  3. 此时基数元素在其排序后的正确位置。
  4. 对左右两部分进行同样方法的排序,直到整个序列有序。

时间复杂度O(nlog2n)

空间复杂度O(nlog2n)

代码示例

	/**
	 * 快速排序算法
	 * 
	 * @param a
	 */
	public void quickSort(int[] a) {
		if (a.length > 0) {
			quick(a, 0, a.length - 1);
		}
	}

	/**
	 * 进行递归
	 * 
	 * @param a    排序的数组
	 * @param low  分段中最小的数的下标
	 * @param high 分段中最大的数的下标
	 */
	public void quick(int[] a, int low, int high) {
		// 控制递归,避免死循环
		if (low < high) {
			int middle = getMiddle(a, low, high);
			// 递归,继续将分段后的两端,分别排序
			quick(a, 0, middle - 1);
			quick(a, middle + 1, high);
		}
	}

	/**
	 * 获取中间下标,即要插入的位置。也是分段的中间位置 同时已将基数插入到相应的位置。
	 * 
	 * @param a    排序的数组
	 * @param low  分段中最小的数的下标
	 * @param high 分段中最大的数的下标
	 * @return 返回插入的位置,也是分段的中间位置
	 */
	private int getMiddle(int[] a, int low, int high) {
		int temp = a[low];// 基数,基准元素
		// 循环寻找位置,直到low>high找到基数应该插入的位置
		while (low < high) {
			while (low < high && a[high] >= temp) {
				high--;
			}
			// 将右边的数丢给左边,左边开始计算
			a[low] = a[high];
			while (low < high && a[low] <= temp) {
				low++;
			}
			// 将左边的数丢给右边,右边开始计算
			a[high] = a[low];
		}
		// 插入到排序后正确的位置
		a[low] = temp;
		return low;
	}

7. 归并排序(Merge Sort)


基本思想:将数组划分为小数组,最终划为一个个长度为1的数组,然后进行依次合并,每次合并后的子序列都是有序的。即:把待排序序列分为若干个长度为1的子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

时间复杂度O(nlog2n)

空间复杂度O(n)

大致实现机制:

  1. 将数组划分为一个个单位为1的子序列。(单位为1的子序列必然是有序的)
  2. 进行子序列的合并。
    a.将要合并的两个子序列拿到。
    b.将子序列进行比较:a[0]>b[0]
    c.将小的那一个序列中的数添加到新数组中。例如 a[0]小于b[0]。newArray[0]=a[0]
    d.将a数组下标右移,继续比较:a[1]>b[0]。依旧是小的那个添加到新数组中。然后将下标+1。
    e.直到其中一方的下标到达其末尾为止。例如:二分时:前面的序列:到middle位置。后面的序列:到a.length-1位置。
    f.将没有移动完的数组剩下的内容直接依次添加到新数组中。(一定比已添加的大,又因为是有序的,因此直接加在新数组后面即可。)
    g.至此,合并操作完成。
  3. 递归合并,直到整体序列有序。

代码示例

	/**
	 * 归并排序
	 * 
	 * @param a
	 */
	public void mergeSort(int[] a) {
		mergeSort(a, 0, a.length - 1);
	}

	/**
	 * 将数组划分,然后合并。按照合并规则,进行排序。
	 * 
	 * @param a     排序的数组
	 * @param left  左边开始的数组下标
	 * @param right 右边结束的数组下标
	 */
	public void mergeSort(int[] a, int left, int right) {
		if (left < right) {
			int middle = (left + right) / 2;
			// 左边的数组
			mergeSort(a, left, middle);
			// 右边的数组
			mergeSort(a, middle + 1, right);
			// 合并
			merge(a, left, middle, right);
		}
	}

	/**
	 * 进行合并数组
	 * 
	 * @param a      需要合并的数组
	 * @param left   左边的起始下标
	 * @param middle 中间的下标
	 * @param right  右边的下标
	 */
	private void merge(int[] a, int left, int middle, int right) {
		int[] tempArray = new int[a.length];
		int rightStart = middle + 1;//
		int tmp = left;
		int third = left;
		while (left <= middle && rightStart <= right) {
			if (a[left] <= a[rightStart]) {
				// tempArray[third++] = a[left++];
				tempArray[third] = a[left];
				third++;
				left++;
			} else {
				tempArray[third++] = a[rightStart++];
			}
		}

		// 如果左边还有数据需要拷贝,把左边数组剩下的,拷贝到新数组
		while (left <= middle) {
			tempArray[third++] = a[left++];
		}
		// 如果右边还有数据需要拷贝,把右边数组剩下的,拷贝到新数组
		while (rightStart <= right) {
			tempArray[third++] = a[rightStart++];
		}
		// 将元素拷贝回a数组
		while (tmp <= right) {
			a[tmp] = tempArray[tmp++];
		}
	}

8. 基数排序(Radix Sort)

基本思想:将数组按照0-9进行划分,分别进行个位,十位,百位…数的排序。依次按照0-9下标进行排序,总共进行最大数的位数次(例如,百位进行三次),然后汇总成一个整体有序的数列。

实现步骤

  1. 找出最大数。计算需要进行排序的次数。=>最大数的位数。
  2. 创建一个二维数组,里面创建十个一维数组。用于表示0-9的数。
  3. 遍历数组,通过0-9存储数据。第一次通过个位数0-9依次排序,第二次通过十位数0-9排序…
  4. 第一次找出各个数个位数的值,将个位为0的数,放在下标为0小数组中,将个位为1的数,放在下标为1小数组中,将个位为2的数,放在下标为2小数组中…依次类推,直到所有数都放到自己的位置。(下标为n的小数组=>即为二维数组中各个小数组对应的下标)
  5. 第一次找出各个数十位数的值,将十位数为0的数,放在下标为0小数组中,将十位数为1的数,放在下标为1小数组中,将十位数为2的数,放在下标为2小数组中…依次类推,直到所有数都放到自己的位置。
  6. 继续执行上面的操作,直到执行到最大数的位数。
  7. 将得到的二维数组中的数依次取出来,序列即为有序数列。

时间复杂度O(d(r+n))

空间复杂度O(rd+n)

:r代表关键字的基数,d代表长度,n代表关键字的个数。

代码示例:

	/**
	 * 基数排序
	 * 
	 * @param a
	 */
	public void radixSort(int[] a) {
		// 获取最大值,决定后面要比较几趟 有几位数就排几次
		int max = a[0];
		for (int i = 0; i < a.length; i++) {
			if (max < a[i]) {
				max = a[i];
			}
		}
		// 次数,记录最大数的位数
		int times = 0;
		while (max > 0) {
			max = max / 10;
			times++;
		}
		// 多维数组
		List<ArrayList<Integer>> queue = new ArrayList<ArrayList<Integer>>();
		// 二维数组中放十个一维小数组
		for (int i = 0; i < 10; i++) {
			// 小数组为0-9,用来分0-9模块存储数据
			ArrayList<Integer> q = new ArrayList<Integer>();
			queue.add(q);
		}
		// 遍历存储,通过0-9存储数据。第一次通过个位数0-9依次排序,第二次通过十位数0-9排序....
		for (int i = 0; i < times; i++) {
			for (int j = 0; j < a.length; j++) {
				// 获取对应位的值==> i=0:获取个位数,i=1:获取十位数,i=2:获取百位数
				int x = a[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);
				// 获取到所属的小数组下标位置
				ArrayList<Integer> q = queue.get(x);
				// 把元素添加进对应的下标数组
				q.add(a[j]);
				queue.set(x, q);
			}
			// 开始收集
			int count = 0;
			for (int j = 0; j < 10; j++) {
				while (queue.get(j).size() > 0) {
					// 拿到每一个数组
					ArrayList<Integer> q = queue.get(j);
					a[count] = q.get(0);
					q.remove(0);
					count++;
				}
			}
		}
	}

总结


八大排序算法比较:
在这里插入图片描述
本文为自己学习总结,欢迎各位大神进行指正以及交流讨论。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值