排序算法简述

常见排序算法基本信息
在这里插入图片描述

一、冒泡排序

1.1 冒泡排序简介

    冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。
    冒泡排序还有一种优化算法,就是立一个 flag,当在一趟序列遍历中元素没有发生交换,则证明该序列已经有序。但这种改进对于提升性能来说并没有什么太大作用。

1.2 算法执行步骤与图解

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
    在这里插入图片描述

1.3 java程序实现

public int[] BubbleSort(int[] nums) {
    
    
	int len = nums.length;
	for (int i = 0; i < len; i++) {
    
    
    	boolean flag = true;
        for (int j = 0; j < len - 1; j++) {
    
    
        	if (nums[j] > nums[j + 1]) {
    
    
            	flag = false;
                int temp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
             }
        }
        if (flag) {
    
    
        	break;
		}
	}
	return nums;
}

二、选择排序

2.1 选择排序简介

    选择排序是一种简单直观的排序算法,它的工作原理是每一次从待排序的数据元素中选出最小(最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。选择排序是不稳定的排序方法。

2.2 算法执行步骤与图解

  1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
  2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
  3. 重复第二步,直到所有元素均排序完毕。
    在这里插入图片描述

2.3 java程序实现

public int[] SelectionSort(int[] nums) {
    
    
	int len = nums.length;
	for (int i = 0; i < len; i++) {
    
    
		int index = i;
		for (int j = i + 1; j < len; j++) {
    
    
			if (nums[index] > nums[j]) {
    
    
				index = j;
			}
		}
		if (index != i) {
    
    
			int temp = nums[i];
			nums[i] = nums[index];
			nums[index] = temp;
		}
	}
	return nums;
}

三、插入排序

3.1 插入排序简介

    对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入,直到插完所有元素为止。
    插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。所以在数据量小数据基本有序的情况下用插入排序最优。

3.2 算法执行步骤与图解

  1. 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
  2. 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
    在这里插入图片描述

3.3 java程序实现

public int[] InsertSort(int[] nums) {
    
    
	int len = nums.length;
	//i表示前i个数字为一个有序序列
	for (int i = 1; i < len; i++) {
    
    
		//记录第i个数字(要插入有序序列,边比较,边移位,即将第i个位置视为temp)
		int temp = nums[i];
		int j = i - 1;
		//遍历有序序列,直到到达有序序列边界或者找到比自己小的序列为止
		while (j >= 0 && temp < nums[j]) {
    
    
			//在找的过程中边移位
			nums[j + 1] = nums[j];
			j--;
		}
		//到达边界,或者找到比自己小的数字的位置
		nums[j + 1] = temp;
	}
    return nums;
}

四、希尔排序

4.1 希尔排序简介

    希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
    插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。

4.2 算法执行步骤

  1. 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
  2. 按增量序列个数 k,对序列进行 k 趟排序;
  3. 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
    在这里插入图片描述

4.3 java程序实现

public int[] shellSort(int[] nums) {
    
    
	int len = nums.length;
	for (int step = len / 2; step >= 1; step /= 2) {
    
    
		//做法与插入排序相似,只是步数从1变为step
		for (int i = step; i < len; i++) {
    
    
			int temp = nums[i];
			int j = i - step;
			while (j >= 0 && temp < nums[j]) {
    
    
				nums[j + step] = nums[j];
                j -= step;
            }
            nums[j + step] = temp;
		}
	}
	return nums;
}

五、归并排序

5.1 归并排序简介

    归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
在这里插入图片描述

5.2 算法步骤与图解

1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
4. 重复步骤 3 直到某一指针达到序列尾;
5. 将另一序列剩下的所有元素直接复制到合并序列尾。
在这里插入图片描述

5.3 java程序实现

//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
private int[] temp;

public int[] MergeSort(int[] nums) {
    
    
	int len = nums.length;
	temp = new int[len];
	Sort(0, len - 1, nums);
	return nums;
}

private void Sort(int left, int right, int[] arr) {
    
    
	if (left < right) {
    
    
		int mid = (left + right) / 2;
		Sort(left, mid, arr);//左边归并排序,使左边有序
		Sort(mid + 1, right, arr);//右边归并排序,使右边有序
		margin(left, mid, right, arr);//将左右两边合并成一个有序数组
	}
}

private void margin(int left, int mid, int right, int[] arr) {
    
    
	//排序left指向左边有序数组中最小的元素
	//mid + 1指向右边有序数组中最小的元素
	int x = left;
	int y = mid + 1;
	int index = left;
	//当左边右边都还有元素时,那个小那个先放入temp中
	while (x <= mid && y <= right) {
    
    
		if (arr[x] < arr[y]) {
    
    
			temp[index++] = arr[x++];
		} else {
    
    
			temp[index++] = arr[y++];
		}
	}

	//若左边还剩元素,则将左边元素放入temp中
	while (x <= mid) {
    
    
		temp[index++] = arr[x++];
	}
	//若右边还剩元素,则将左边元素放入temp中
	while (y <= right) {
    
    
		temp[index++] = arr[y++];
	}

	//将temp中的元素放回arr中
	for (int i = left; i < right + 1; i++) {
    
    
		arr[i] = temp[i];
	}
}

六、快速排序

6.1 快排简介

    快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
    快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。
    快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
    快速排序的最坏运行情况是 O(n²),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。

6.2 算法步骤与图解

  1. 从数列中挑出一个元素,称为 “基准”(pivot);
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

    快速排序简介

6.3 java程序实现

public int[] QuickSort(int[] nums) {
    
    
	int len = nums.length;
	Sort2(0, len - 1, nums);
	return nums;
}

private void Sort2(int left, int right, int[] arr) {
    
    
	if (left < right) {
    
    
	int partition = getPartition(left, right, arr);
		Sort2(left, partition - 1, arr);
		Sort2(partition + 1, right, arr);
	}
}

private int getPartition(int left, int right, int[] arr) {
    
    
	//以最右边的元素为基准值
	int pivot = right;
	while (left < right) {
    
    
		//找到比pivot更大的值
		while (left < right) {
    
    
			if (arr[left] > arr[pivot]) {
    
    
				break;
	        }
	        left++;
		}
		//找到比pivot更小的值
		while (left < right) {
    
    
			if (arr[right] < arr[pivot]) {
    
    
	        	break;
	        }
	        right--;
		}
	    Swap(left, right, arr);
	}
    Swap(left, pivot, arr);
    return right;
}

private void Swap(int x, int y, int[] arr) {
    
    
	int temp = arr[x];
	arr[x] = arr[y];
	arr[y] = temp;
}

private void Swap(int x, int y, int[] arr) {
    
    
	int temp = arr[x];
	arr[x] = arr[y];
	arr[y] = temp;
}

七、堆排序

7.1 堆排序简介

    堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

  • 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
  • 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;

堆排序的平均时间复杂度为 Ο(nlogn)。

7.2 算法执行步骤与图解

  1. 创建一个堆 H[0……n-1];
  2. 把堆首(最大值)和堆尾互换;
  3. 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
  4. 重复步骤 2,直到堆的尺寸为 1。
    在这里插入图片描述

java程序实现

大顶堆:

public int[] HeapSort_1(int[] nums) {
    
    
	int len = nums.length;
	//按照二叉树的定义,第一个非叶子节点位置在len/2-1处,每次--即可遍历所有非叶子节点,知道遍历到根节点
	for (int i = len / 2 - 1; i >= 0; i--) {
    
    
		heapify_1(nums, i, len);
	}
	for (int i = len - 1; i > 0; i--) {
    
    
		Swap(i, 0, nums);
		len = len - 1;
		heapify_1(nums, 0, len);
	}
	return nums;
}

/**
 * 堆调整,使得根节点的值最大
 *
 * @param arr:堆
 * @param root:根节点
 */
private void heapify_1(int[] arr, int root, int len) {
    
    
	//按照二叉树的性质,左边叶子节点为根节点位置的两倍加1,右边叶子节点为根节点的2倍加2
	int left = root * 2 + 1;
	int right = root * 2 + 2;
	int largest = root;
	if (left < len && arr[left] > arr[largest]) {
    
    
		largest = left;
	}
	if (right < len && arr[right] > arr[largest]) {
    
    
		largest = right;
	}
	if (largest != root) {
    
    
		Swap(largest, root, arr);
        heapify_1(arr, largest, len);
    }
}
private void Swap(int x, int y, int[] arr) {
    
    
	int temp = arr[x];
	arr[x] = arr[y];
	arr[y] = temp;
}

小顶堆

public int[] HeapSort_2(int[] nums) {
    
    
	int len = nums.length;
	//按照二叉树的定义,第一个非叶子节点位置在len/2-1处,每次--即可遍历所有非叶子节点,知道遍历到根节点
	for (int i = len / 2 - 1; i >= 0; i--) {
    
    
		heapify_2(nums, i, len);
	}
	for (int i = len - 1; i > 0; i--) {
    
    
		Swap(i, 0, nums);
		len = len - 1;
		heapify_2(nums, 0, len);
	}
	return nums;
}

/**
 * 堆调整,使得根节点的值最小
 *
 * @param arr:堆
 * @param root:根节点
 */
private void heapify_2(int[] arr, int root, int len) {
    
    
	//按照二叉树的性质,左边叶子节点为根节点位置的两倍加1,右边叶子节点为根节点的2倍加2
	int left = root * 2 + 1;
	int right = root * 2 + 2;
	int largest = root;
	if (left < len && arr[left] < arr[largest]) {
    
    
		largest = left;
	}
	if (right < len && arr[right] < arr[largest]) {
    
    
		largest = right;
	}
	if (largest != root) {
    
    
		Swap(largest, root, arr);
        heapify_2(arr, largest, len);
    }
}
private void Swap(int x, int y, int[] arr) {
    
    
	int temp = arr[x];
	arr[x] = arr[y];
	arr[y] = temp;
}

八、计数排序

8.1 计数排序简介

    计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
计数排序的特征
    当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 Θ(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。
    由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序中的算法来排序数据范围很大的数组。

8.2 算法执行步骤与图解

  1. 找出待排序的数组中最大和最小的元素
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
  3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
    在这里插入图片描述

java程序实现

public void CountingSort(int[] nums) {
    
    
	int min = nums[0];
	int max = nums[0];
	for (int i = 1; i < nums.length; i++) {
    
    
		if (nums[i] < min) {
    
    
			min = nums[i];
            continue;
		}
        if (nums[i] > max) {
    
    
			max = nums[i];
		}
	}
    int[] maxAndMin = new int[max - min + 1];
    for (int i : nums) {
    
    
		maxAndMin[i - min]++;
	}
	int index = 0;
	for (int i = 0; i < maxAndMin.length; i++) {
    
    
		int num = maxAndMin[i];
		while (num > 0) {
    
    
			nums[index++] = i + min;
            num--;
		}
	}
}

各排序算法运行结果

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/tyh1579152915/article/details/113000145
今日推荐