【数据结构】之排序算法综合篇

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/smile_Running/article/details/95874569

博主声明:

转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。

本文首发于此   博主威威喵  |  博客主页https://blog.csdn.net/smile_running

    数据结构课程的内容基本忘光了,所以又拾起数据结构与算法的书本,重新学习了一边排序算法的原理和实现,在经过几个晚上的努力,终于把这几种排序算法给搞明白了,下面分享一下总结。注:以下博文中的动态图并非我画的,自行百度以下排序算法动态图就有一大堆了,很感谢那些画图的原作者前辈们,让我们理解起来更加简单,也更加形象。

    接下来介绍一下排序算法的概念,下面直接引用百科资料:

    【排序就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。排序算法,就是如何使得记录按照要求排列的方法。排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面。一个优秀的算法可以节省大量的资源。在各个领域中考虑到数据的各种限制和规范,要得到一个符合实际的优秀算法,得经过大量的推理和分析。】

    数据结构之常用的排序算法列举:

  1. 冒泡排序
  2. 选择排序
  3. 插入排序
  4. 希尔排序
  5. 归并排序
  6. 快速排序
  7. 基数排序
  8. 堆排序

一张图看一下几种排序算法的时间复杂度与空间复杂度情况:

    接下来,我们直接看每一种排序算法的原理以及代码实现,我使用的是 Java 语言编写的,本来是想用 C 语言写的,奈何大一学习的 C 如今早已抛掷脑后了,惭愧,惭愧。话不多说,我们直接看第一种排序,正文开始。

1、冒泡排序

    冒泡排序是我们接触任何语言时一定会提及的一种排序算法,它是入门的必学算法之一。我在大一刚学 C 语言时,也时常被其困扰,为什么我徒手写不出一个冒泡排序出来,后面我才发现,关键还是没理解它的原理。

    我现在发现,学习任何语言,或者说学习任何知识也好,首先你得搞清楚它的原理,这是至关重要的。那么我们来看看冒泡排序算法的实现原理:总结为一句话,比较相邻的两个元素,每次比较完后较大的一个数移动到本轮的末尾。

    你可以想象一下这样,有一个小学生队伍需要进行从低到高排队,反正小学经常干这种事情。我通常与前面一位同学先比较,看看谁比较高,透露一个秘密,我小学时候好矮,经常排前面,坐第一排都是习惯就好。这里的冒泡法的对象是对于我而言,站在我的角度去与相邻的同学之间做比较。

冒泡排序的效果图:

 冒泡排序的代码实现:

package com.xww;

import java.util.Arrays;

/**
 * 冒泡排序(原理:比较相邻的两个元素,每次比较完毕最大的一个数移动到本轮的末尾。)
 * 
 * @author xww
 * 
 */
public class BubbleSort {

	// 无序数组
	static int arr[] = new int[] { 2, 7, 6, 1, 8, 3, 5, 9, 6, -1, -4, 3 };

	public static void main(String[] args) {
		System.out.println(Arrays.toString(arr));
		int len = arr.length;
		// 循环n-1趟
		for (int i = 0; i < len - 1; i++) {
			// 每一趟移除已经排序过的,取剩下未排序的进行比较
			for (int j = 0; j < len - 1 - i; j++) {
				// 如果前面一个数比后面一个数大,交换两个数的位置
				if (arr[j] > arr[j + 1]) {
					arr[j] ^= arr[j + 1];
					arr[j + 1] ^= arr[j];
					arr[j] ^= arr[j + 1];
				}
			}
		}
		System.out.print(Arrays.toString(arr));
	}
}

排序结果如下图:

 

    在判断两个数的值大小是,冒泡排序采用的是交互两个数的位置,我上面采用的是一种二进制 ^ 计算,不用引入一个临时变量,直接对其进行交换。(下同)

2、选择排序

    选择排序也是入门必定会接触的排序算法之一,和冒泡排序一样非常实用,可能会比冒泡理解起来困难一点点,因为它引入的是一个 index 变量来保存每一趟循环计算出的最大(小)的那个数的位置。它的实现原理:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

    选择排序你可以想象为这样,还是以上面的排队为例子,老师看我们排队排的乱七八糟、参差不齐的,于是老师又进行了一遍排队操作。首先他以第一个小朋友为基准,与后面一个比较,发现第一个小朋友比较高,给他俩交换位置,又发现高了后面一个小朋友,又交换位置,直到给他拎到末尾去结束一轮排序。

    第二轮从第二个小朋友开始,一直拎,直到全部拎完结束排序。哈哈哈哈,不要问为什么用拎,反正我小学老师在我们排队回家的时候,基本都是这样干的,小学一二年级非常小,才 8 岁这样,老师就跟夹娃娃一样。

选择排序的效果图:

  选择排序的代码实现:

package com.xww;

import java.util.Arrays;

/**
 * 选择排序(原理:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,
 * 然后放到已排序序列的末尾。)
 * 
 * @author xww
 * 
 */
public class SelectionSort {

	// 无序数组
	static int arr[] = new int[] { 2, 7, 5, -1, 8, 3, 1, 5, 9, 6, -3, 4 };

	public static void main(String[] args) {
		System.out.println(Arrays.toString(arr));
		int len = arr.length;
		int index;// 标记下标
		for (int i = 0; i < len - 1; i++) {
			// 标记前一个数
			index = i;
			// 与后面剩余的其他数依次做比较
			for (int j = i + 1; j < len; j++) {
				// 如果被标记的数比较大,则交换两数的位置
				if (arr[j] < arr[index]) {
					arr[j] ^= arr[index];
					arr[index] ^= arr[j];
					arr[j] ^= arr[index];
				}
			}
		}
		System.out.print(Arrays.toString(arr));
	}
}

排序结果如下图:

3、 插入排序

    插入排序呢,算是我们小时候就会的了,这个怎么说呢,比如我们小时候肯定都有玩过纸牌游戏吧(斗地主),我们取排的时候,按照我的习惯来说,我是比较喜欢把牌从小到大进行排列的,一个为了方便查找,不容易把牌给看漏掉,另一个就是为了装大手,别人出牌的那一刹那,我就把牌给扔下去了, 显得自己牌技高超,哈哈哈哈。

    不过呢,与插入排序联系上的是,没当我们取一张牌的时候,可能是比较大,也可能比较小,这时候我们就要进行比较了,看看应该把牌插入到哪个位置,很显然这就用到了插入排序的算法,我靠,这不是人人都会嘛。

    插入排序原理及算法步骤:

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

插入排序的效果图:

   插入排序的代码实现:

package com.xww;

import java.util.Arrays;

/**
 * 插入排序(原理:插入排序的工作方式非常像人们排序一手扑克牌一样。)【步骤】1、从第一个元素开始,该元素可以认为已经被排序。
 * 2、取出下一个元素,在已经排序的元素序列中从后向前扫描。 3、如果该元素(已排序)大于新元素,将该元素移到下一位置。
 * 4、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置。 5、将新元素插入到该位置后 重复步骤2~5
 * 
 * @author xww
 *
 */
public class InsertionSort {

	// 无序数组
	static int arr[] = new int[] { 2, 7, -1, 8, 3, 1, 5, -4, 6, -3, 4 };

	public static void main(String[] args) {
		System.out.println(Arrays.toString(arr));
		int len = arr.length;
		for (int i = 1; i < len; i++) {
			// 取得后一个数
			int key = arr[i];
			int j = i - 1;
			// 与前一个做比较,如果大于后面的数,则把前一个数插入到后面的位置
			while (j >= 0 && arr[j] > key) {
				arr[j + 1] = arr[j];
				j = j - 1;
			}
			// 如果前面一个数小于后面一个数,key就往后移动一位
			arr[j + 1] = key;
		}
		System.out.print(Arrays.toString(arr));
	}
}

排序结果如下图:

4、希尔排序

    希尔排序有点不好理解吧,希尔排序是对插入排序的优化。为了减少数据的移动次数,在初始序列较大时取较大的步长,通常取序列长度的一半,此时只有两个元素比较,交换一次;之后步长依次减半直至步长为1,即为插入排序,由于此时序列已接近有序,故插入元素时数据移动的次数会相对较少,效率得到了提高。

    它的实现原理:选择增量 gap=length/2,缩小增量继续以 gap = gap/2 的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2...1},称为增量序列。

希尔排序的效果图:

    希尔排序的代码实现:

package com.xww;

import java.util.Arrays;

/**
 * 希尔排序(原理:选择增量gap=length/2,缩小增量继续以gap = gap/2
 * 的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2...1},称为增量序列。)
 * 
 * @author xww
 *
 */
public class ShellSort {

	// 无序数组
	static int arr[] = new int[] { -5, 8, 9, 1, 8, 7, 2, -3, 5, 4, -6, -1 };

	public static void main(String[] args) {
		System.out.println(Arrays.toString(arr));
		int len = arr.length;
		int temp;
		int gap = len / 2;
		while (gap > 0) {
			for (int i = gap; i < len; i++) {
				temp = arr[i];
				int preIndex = i - gap;// 获取以 gap 为间隔的前一个值
				while (preIndex >= 0 && arr[preIndex] > temp) {// 比较以 gap 为间隔的两个数,若前一个数比较大
					arr[preIndex + gap] = arr[preIndex];// 将前一个数赋值给后一个数
					preIndex -= gap;// 若 preIndex < 0 ,则 arr[] 将会溢出,此时结束循环体
				}
				arr[preIndex + gap] = temp; // 由于 preIndex -= gap 这里的 arr[preIndex + gap] 其实等于 arr[preIndex] 所以要将后面一个值赋值给 arr[preIndex] ,以作交换
			}
			gap /= 2;// 将间隔 gap 缩小一半,直到为 0
		}
		System.out.print(Arrays.toString(arr));
	}
}

排序结果如下图:

5、归并排序

    归并排序算法描述:归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

    它的算法实现步骤:

  • 把长度为n的输入序列分成两个长度为n/2的子序列
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个 最终的排序序列

归并排序的效果图:

  归并排序的代码实现: 

package com.xww;

import java.util.Arrays;

/**
 * 归并排序(算法描述:归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and
 * Conquer)的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;
 * 即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
 * 
 * 算法步骤:把长度为n的输入序列分成两个长度为n/2的子序列; 对这两个子序列分别采用归并排序; 将两个排序好的子序列合并成一个 最终的排序序列。)
 * 
 * @author xww
 *
 */
public class MergeSort {
	// 无序数组
	static int arr[] = new int[] { 2, 7, 6, 1, 8, 3, 5, 9, 6, -1, -4, 3 };

	public static void main(String[] args) {
		System.out.println(Arrays.toString(arr));

		System.out.print(Arrays.toString(mergeSort(arr)));
	}

	private static int[] mergeSort(int arr[]) {
		int len = arr.length;
		if (len < 2)// 如果数组仅有一个元素,直接返回
			return arr;

		int mid = len / 2;
		// 将数组分成两个子数组
		int left[] = Arrays.copyOfRange(arr, 0, mid);
		int right[] = Arrays.copyOfRange(arr, mid, len);
		return merge(mergeSort(left), mergeSort(right));
	}

	private static int[] merge(int left[], int right[]) {
		// 实例化一个新的数组,大小为给定数组的长度
		int new_arr[] = new int[left.length + right.length];
		int len = new_arr.length;

		int i = 0;
		int j = 0;
		for (int index = 0; index < len; index++) {
			if (i >= left.length)
				new_arr[index] = right[j++];
			else if (j >= right.length)
				new_arr[index] = left[i++];
			else if (left[i] > right[j])
				new_arr[index] = right[j++];
			else
				new_arr[index] = left[i++];
		}
		return new_arr;
	}
}

排序结果如下图:

6、快速排序 

    快速排序比较复杂,(类似于选择排序的定位思想)选一基准元素,依次将剩余元素中小于该基准元素的值放置其左侧,大于等于该基准元素的值放置其右侧。接着取基准元素的前半部分和后半部分分别进行同样的处理。以此类推,直至各子序列剩余一个元素时,即排序完成。

    快速排序的实现方式,我这里写了两种方式:

(1)填坑法:

    1、填坑法 (原理: 首先,选定基准元素 pivot,并标记其位置为 index,这个位置相当于一个“坑”。并且设置两个指针left和right,指向数列的最左和最右两个元素。
    2、接着,从right指针开始,把指针所指向的元素和基准元素做比较。如果比pivot大,则right指针向左移动;如果比pivot小,则把right所指向的元素填入坑中。
    3、再接着,我们切换到left指针进行比较。如果left指向的元素小于pivot,则left指针向右移动;如果元素大于pivot,则把left指向的元素填入坑中。

(2)指针交换法:

    1、首先选定基准元素pivot,并且设置两个指针left和right,指向数列的最左和最右两个元素。从right指针开始,把指针所指向的元素和基准元素做比较。

    2、如果大于等于pivot,则指针向左移动;如果小于pivot,则right指针停止移动,切换到left指针。轮到left指针行动,把指针所指向的元素和基准元素做比较。

    3、如果小于等于pivot,则指针向右移动;如果大于pivot,则left指针停止移动。这时,让left和right指向的元素进行交换。(由于left一开始指向的是基准元素,判断肯定相等,所以left右移一位。)

快速排序的效果图:

   快速排序的代码实现: 

package com.xww;

import java.util.Arrays;

/**
 * 快速排序:一、填坑法 (原理: 首先,选定基准元素 pivot,并标记其位置为
 * index,这个位置相当于一个“坑”。并且设置两个指针left和right, 指向数列的最左和最右两个元素。
 * 接着,从right指针开始,把指针所指向的元素和基准元素做比较。如果比pivot大,则right指针向左移动;如果比pivot小,
 * 则把right所指向的元素填入坑中。
 * 再接着,我们切换到left指针进行比较。如果left指向的元素小于pivot,则left指针向右移动;如果元素大于pivot,
 * 则把left指向的元素填入坑中。)
 * 
 * 二、指针交换法(原理:首先选定基准元素pivot,并且设置两个指针left和right,指向数列的最左和最右两个元素。从right指针开始,
 * 把指针所指向的元素和基准元素做比较。如果大于等于pivot,则指针向左移动;如果小于pivot,则right指针停止移动,切换到left指针。
 * 轮到left指针行动,把指针所指向的元素和基准元素做比较。如果小于等于pivot,则指针向右移动;如果大于pivot,则left指针停止移动。
 * 这时,让left和right指向的元素进行交换。 [由于left一开始指向的是基准元素,判断肯定相等,所以left右移一位。])
 * 
 * @author xww
 * 
 */
public class QuickSort {

	static int arr[] = new int[] { 6, 7, -6, 5, 3, 2, -2, 8, 4, 9, 7, 1 };

	public static void main(String[] args) {

		System.out.println(Arrays.toString(arr));

		quickSortFill(arr, 0, arr.length - 1);

		arr = new int[] { 6, 7, -6, 5, 3, 2, -2, 8, 4, 9, 7, 1 };

		System.out.println("填坑法:" + Arrays.toString(arr));

		quickSortPointer(arr, 0, arr.length - 1);

		System.out.println("指针交换法:" + Arrays.toString(arr));
	}

	/**
	 * 填坑法,递归调用
	 */
	private static void quickSortFill(int arr[], int left, int right) {
		// 递归结束条件:left大等于right的时候
		if (left >= right) {
			return;
		}

		int pivotIndex = fillSort(arr, left, right);

		quickSortFill(arr, left, pivotIndex - 1);
		quickSortFill(arr, pivotIndex + 1, right);
	}

	/**
	 * 指针交换法,递归调用
	 */
	private static void quickSortPointer(int arr[], int left, int right) {
		if (left >= right) {
			return;
		}
		// 得到基准元素位置
		int pivotIndex = pointerSort(arr, left, right);
		// 根据基准元素,分成两部分递归排序
		quickSortPointer(arr, left, pivotIndex - 1);
		quickSortPointer(arr, pivotIndex + 1, right);
	}

	/**
	 * 填坑法
	 */
	private static int fillSort(int arr[], int left, int right) {
		// 取第一个位置的元素作为基准元素
		int pivot = arr[left];
		// 坑的位置,初始等于pivot的位置
		int index = left;

		while (right >= left) {
			// right从右向左进行比较
			while (right >= left) {

				if (arr[right] < pivot) {
					arr[index] = arr[right];
					index = right;
					left++;
					break;
				}
				right--;
			}

			// left从左向右进行比较
			while (right >= left) {
				if (arr[left] > pivot) {
					arr[index] = arr[left];
					index = left;
					right--;
					break;
				}
				left++;
			}
		}

		arr[index] = pivot;
		return index;
	}

	/**
	 * 指针交换法
	 */
	private static int pointerSort(int arr[], int left, int right) {
		int pivot = arr[left];
		int startIndex = left;

		// 如果left与right不相等,说明此趟排序未结束
		while (left != right) {
			// 控制right指针比较并左移
			while (left < right && arr[right] > pivot) {
				right--;
			}
			// 控制right指针比较并右移
			while (left < right && arr[left] <= pivot) {
				left++;
			}
			// 交换left和right指向的元素
			if (left < right) {
				int temp = arr[left];
				arr[left] = arr[right];
				arr[right] = temp;
			}
		}
		// pivot和指针重合点交换
		int temp = arr[left];
		arr[left] = arr[startIndex];
		arr[startIndex] = temp;
		return left;
	}
}

排序结果如下图:

 以上是几种常用的排序算法,还有堆排序、基数排序,本人还没写,以后再补。

猜你喜欢

转载自blog.csdn.net/smile_Running/article/details/95874569