常见的八种排序算法

本文介绍了八种常见的排序算法,包括冒泡排序、选择排序、归并排序、快速排序、链式基数排序、插入排序、希尔排序和堆排序。详细讲述了每种排序算法的工作原理和步骤,其中链式基数排序适用于多关键字排序,希尔排序通过增量分组提高插入排序效率,堆排序则利用了完全二叉树的概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

排序按类别分为:插入排序(插入排序、希尔排序)、选择排序(选择排序、堆排序)、交换排序(冒泡排序、快速排序)、归并排序、基数排序。

1. 冒泡排序

/**
	 * 从第一个数据开始,将顺序表中的每一个数据与其后一个数据比较大小 如果比后边大,就交换位置, 直到倒数第二个与倒数第一个比较完毕
	 * 我们可以得到最大的数,放在队尾, 不断循环执行,直到数组有序
	 */
	public static void bubbleSort(int[] arr) {
		for (int i = 0; i < arr.length - 1; i++) {
			for (int j = 0; j < arr.length - i - 1; j++) {
				if (arr[j] > arr[j + 1]) {
					int temp = arr[j];
					arr[j] = arr[j + 1];
					arr[j + 1] = temp;
				}
			}
		}
	}

2. 选择排序

/**
	 * 选择数列中下表为0的数作为index,将其后面的数与index指向的数作比较
	 * 若后面的数比index指向的数小,就把index指向后面的数,直到比较到数列尾,
	 * 此时指向数列中最小的数,然后选择下标为1的数作为index,继续进行选择排序,直到结束
	 */
	public static void selectSort(int[] arr) {
		for (int i = 0; i < arr.length - 1; i++) {
			for (int j = i + 1; j < arr.length; j++) {
				if (arr[i] > arr[j]) {
					int temp = arr[i];
					arr[i] = arr[j];
					arr[j] = temp;
				}
			}
		}
	}

3. 归并排序

/**
	 * 将一个数列从中间分为两个有序的数列,然后再将两个数列按照从小到大的顺序合并成一个有序数列
	 * 时间复杂度:O(nlogn)
	 * 空间复杂度:O(N),归并排序需要一个与原数组相同长度的数组做辅助
	 * 
	 * 稳定性:归并排序是最稳定的排序算法,
	 * 当左右部分值相等时,
	 * 先复制左边的值,保证两个值相等的元素的相对位置不变
	 */
	public static void mergeSort(int arr[], int left, int right){
		if(left == right){
			return;
		}else{
			int mid = (left+right)/2;
    			mergeSort(arr, left, mid);
			mergeSort(arr, mid+1, right);
			merge(arr, left, mid, right);
		}
	}
	public static void merge(int[] arr, int left, int mid, int right){
		int[] tmp = new int[right - left + 1];
		int i = 0;
		int p1 = left;
		int p2 = mid+1;
//		比较左右两部分元素,那个小,把那个元素填入tmp中
		while(p1 <= mid && p2 <= right){
			tmp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
		}
//		在上面循环退出后,把剩余元素依次填入到tmp中
		while(p1 <= mid){
			tmp[i++] = arr[p1++];
		}
		while(p2 <= right){
			tmp[i++] = arr[p2++];
		}
//		把最终排序结果赋值给原数组
		for(i = 0; i < tmp.length; i++){
			arr[left + i] = tmp[i];
		}
	}

4. 快速排序

/**
	 * 在数列中选择一个数作为基准,用其他的数与该数进行比较, 比他大的放右边,比他小的放左边 这样一轮比较完成后,左边的数都比它小,右边的数都比它大
	 * 以该数为中间点,将数列分成两部分, 分别进行同样的操作,直到数列有序
	 */
	public static void quickSort(int[] arr, int low, int high) {

		if (low < high) {
			int index = getIndex(arr, low, high);
			quickSort(arr, 0, index - 1);
			quickSort(arr, index + 1, high);
		}
	}

	private static int getIndex(int[] arr, int low, int high) {
		// 基准数据
		int tmp = arr[low];
		while (low < high) {
			// 当队尾的元素大于等于基准数据时,向前挪动high指针
			while (low < high && arr[high] >= tmp) {
				high--;
			}
			// 如果队尾元素小于tmp了,需要将其值赋给low
			arr[low] = arr[high];
			// 当队首元素小于等于tmp时,向前挪动low指针
			while (low < high && arr[low] <= tmp) {
				low++;
			}
			arr[high] = arr[low];
		}
//		跳出循环时low和high相等,此时的low或high就是tmp的正确索引位置
		arr[low] = tmp;
		return low;
	}

5. 链式基数排序

基数排序采用“分配”与“收集”的办法,用对多关键码进行排序的思想实现对单关键码进行排序的方法。

思路:使用链表存储数据,通过调整指针实现LSD(低关键字优先)算法进行排序。

举个例子就是我们在qq游戏上的纸牌或麻将,从服务器端返回来一组数据,纸牌和麻将都是多关键字的对象,纸牌和麻将都是有点数和花色划分的,我们要对这组数据进行排序,那使用链式基数排序效率是最快的。

而且这种排序是使用在对原始数据的排序,如果有新数据添加进来的话,那就使用希尔排序。

步骤:

(1)先初始化一个要存放的点数的集合数组,根据点数把数据放入对应的数组中,把点数为1的数放入数组为0的下标中, 点数为2的数据放入1的下标中,数组中每个下标存放数据的都是一个集合,这样就可以把数值为1~9的麻将分别存入对应的数组中。然后再把数组中的集合按顺序依次添加到一个新的集合中。

(2)初始化一个存放花色的集合数组,根据花色把数据放入对应的组中,把花色为万字的数据放入数组为0的下标中,花色为条字的数据放入数组为1的下标中,花色为筒字的数据放入数组为2的下标中,这样就可以把花色为万筒条的数据都存入对应的数组中。最后再把数组中的集合桉树按顺序依次添加到一个新的集合当中。

 

/**
	 * 链式基数排序
	 * 例如给麻将排序,
	 * 先按照点数大小排成一个链表,然后根据类别,分成三个链表
	 * 分别按照大小顺序存放不同类别的麻将
	 * 最后将这三个链表连接起来,得到最终排好序的麻将结果
	 * 
	 * 使用场景:
	 * 多关键字排序,只适合十几个数据以内,数据量不大的情况
	 * 斗地主纸牌游戏, 麻将初始化排序
	 */
	public static class Mahjong{
		public int suit; //花色
		public int rank; //点数
		public Mahjong(int suit, int rank){
			this.suit = suit;
			this.rank = rank;
		}
		@Override
		public String toString() {
			return "Mahjong [suit=" + suit + ", rank=" + rank + "]";
		}
	}
	public static void radixSort(LinkedList<Mahjong> list){
//		先初始化要存放点数的集合数组
		LinkedList[] rankList = new LinkedList[9];
		for(int i = 0; i < rankList.length; i++){
			rankList[i] = new LinkedList<>();//让每个数组都放置链表集合
		}
//		将数据依次放入对应组中
		while(list.size() > 0){
			Mahjong m = list.remove();
//			放入组中,下标为点数减一
			rankList[m.rank-1].add(m); // 麻将点数减一为放入的数组下标
		}
//		把9个数组数据连接起来,形成一个链表
		for(int i = 0; i < rankList.length; i++){
			list.addAll(rankList[i]);
		}
//		进行花色分组
		LinkedList[] suitList = new LinkedList[3];
		for(int i = 0; i < suitList.length; i++){
			suitList[i] = new LinkedList<>();
		}
//		把数据一个个放入组中
		while(list.size() > 0){
			Mahjong m = list.remove();
//			放入组中,下标为点数减一
			suitList[m.suit-1].add(m);
		}
		for(int i = 0; i < suitList.length; i++){
			list.addAll(suitList[i]);
		}
	}
	public static void main(String[] args) {
		LinkedList<Mahjong> list = new LinkedList<>();
		list.add(new Mahjong(3, 1));
		list.add(new Mahjong(2, 3));
	    list.add(new Mahjong(3, 7));
	    list.add(new Mahjong(1, 1));
	    list.add(new Mahjong(3, 8));
	    list.add(new Mahjong(2, 2));
	    list.add(new Mahjong(3, 2));
	    list.add(new Mahjong(1, 3));
	    list.add(new Mahjong(3, 9));
	    radixSort(list);
	}

6. 插入排序

/**
	 * 插入排序
	 * 思路:
	 * 从数列的第一个数开始,将其与前面的数进行比较,如果比前面的小,就插入到前面的位置
	 * 直到比较到第零个数到达数列头部
	 * 应用场景:
	 * 主要为希尔排序做准备
	 * 打牌摸牌时的场景
	 */
	public static void insertSort(int[] arr){
		for(int i = 1; i < arr.length; i++){
			int j = i;
			int target = arr[j];
			while(j > 0 && target < arr[j-1]){
				arr[j] = arr[j-1];
				j--;
			}
			arr[j] = target;
		} 
	} 

7. 希尔排序

步骤:

1)首先把较大的数据集合分割成若干个小组(逻辑上分组),然后对每一个小组分别进行插入排序,此时排序所作用的数据量比较小(每个小组),插入的效率比较高。

2)通过增量进行分组,如增量为4,则a[0]与a[4]一组、a[1]与a[5]一组。

3)每个分组进行插入排序后,各分组就变成有序的了,此时整个数组部分有序。

4)然后缩小增量为上一个增量的一半:2,继续划分分组。虽然每个分组的元素个数多了,但由于数组变得部分有序,所以插入效率同样比较高。

5)直到最后增量为1,整个数组被分为一组,在进行插入排序;此时数组已经接近有序,所以插入效率很高。

特点:

希尔排序灵活的运用了插入排序适用于数据量小且越有序排序效率越高的特点;其同时构造出两个特殊条件已达到高校插入,使得希尔排序升级成为可以运用在数据量较大,且数组无序的场景下。

复杂度:

希尔排序的复杂度和增量序列是相关的

稳定性:

非稳定,虽然插入排序是稳定的,但是希尔排序在插入的时候是跳跃性插入的,有可能破坏稳定性。

/**
	 * 希尔排序
	 * 适用于数据量中等的情况下,几万到几十万
	 * 麻将在玩的过程中的重复排序
	 */
	public static void shellSort(int[] arr, int step){
		for(int k = 0; k < step; k++){
			for(int i = k+step; i < arr.length; i += step){
				int j = i;
				int target = arr[j];
				while(j > step-1 && target < arr[j-step]){
					arr[j] = arr[j-step];
					j -= step;
				}
				arr[j] = target;
			}
		}
	}
 法二:更好理解
 public static void shellSort(int[] arr){
		int N = arr.length;
//		先进行分组,最开始时的增量(gap)为数组长度的一半
		for(int gap = N/2; gap > 0; gap /= 2){
//			对各个分组进行插入排序
			for(int i = gap; i < N; i++){
//				将arr[i]插入到所在分组的正确位置
				insertI(arr, gap, i);
			}
		}
	}
	public static void insertI(int[] arr, int gap, int i){
		int inserted = arr[i];
		int j;
//		插入的时候按组进行插入(组内元素两两相隔gap)
		for(j = i-gap; j >= 0 && inserted < arr[j]; j -= gap){
			arr[j+gap] = arr[j];
		}
		arr[j+gap] = inserted;
	}

8. 堆排序

堆排序是指利用堆积树(堆)这种数据结构所设计的一种排序算法,是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。

知识点:

完全二叉树:除了最后一层之外的其他每一层都被完全填充,并且所有节点都保持左对齐。

满二叉树:除了叶子节点之外的每一个节点都有两个孩子,每一层(包含最后一层)都被完全填充。

完满二叉树:除了叶子节点之外的每一个节点都有两个孩子节点。

/**
	 * 堆排序
	 * 利用堆这种数据结构所设计的一种排序算法,
	 * 堆是一个近似完全的二叉树结构,并同时满足堆的性质
	 * (节点的键值或索引总是小于(或者大于)它的父节点
	 * 排序过程:
	 * 1. 从最后一个非叶子节点开始,每三个节点做一次大小比较,最小的做根
	 * 2. 取走整个树的根节点,把最后一个叶子作为根节点
	 * 3. 重复1和2,直到所有节点被取走
	 * 使用情况:
	 * 在大数据情况下找到钱n个数据
	 */
	public static void heapSort(int[] arr, int len){
//		从最后一个非结点开始建堆
		for(int i = len/2 -1; i >= 0; i--){
			maxHeapify(arr, i, len-1);
		}
//		排序,根节点和最后一个叶子节点交换位置
//		换完后,取走根,然后调整堆
		for(int i = len-1; i > 0; i--){
			int temp = arr[0];
			arr[0] = arr[i];
			arr[i] = temp;
			maxHeapify(arr, 0, i-1);
		}
	}
//	建立最大堆
	public static void maxHeapify(int[] arr, int start, int end){
		int dad = start;
		int son = dad*2 + 1;
		while(son <= end){
//			栈出两个子节点中大的雨父节点比较
			if(son+1 <= end && arr[son] < arr[son+1]){
				son++;
			}
			if(arr[dad] > arr[son]){
				return;
			}else{
				int temp = arr[dad];
				arr[dad] = arr[son];
				arr[son] = temp;
//				递归下一层
				dad = son;
				son = dad*2 + 1;
			}
		}
	}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值