概述
首先我们先来了解排序的定义:将杂乱无章的数据元素,通过一定的方法按关键字顺序排列的过程叫做排序。
本文使用 Java 语言对七大基于比较的排序算法进行实现,没有详尽的分类和介绍,适于有一定语言基础的同学阅读。
七大排序算法(均为升序排列)
直接插入排序
基本思想
把待排序的记录按其关键值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。(实际上我们玩扑克牌时,整牌的过程就是直接插入排序)
代码实现
public static void insertSort(int[] array){
// 想象成整理扑克牌,从拿到第二张开始你就要往已经整理好的牌组中插
// 第一个循环是从第二个数字开始一个一个进行整理
for (int i = 1; i < array.length; i++) {
int tmp = array[i]; // 记录当前你拿到的数字(牌) 因为如果数字小就要
int j = i - 1; // 因为最后要赋值,所以定义到外边
// 第二个循环是已经拿到一个数字,对于前面已经排序好的数组,从后往前进行比较
for ( ; j >= 0; j--) {
if(array[j] > tmp) {
// 当前的数字大,赋值给后一个位置
array[j+1] = array[j];
}else {
// 找到应该插入的位置,结束循环
break;
}
}
// 插入该位置后
array[j+1] = tmp;
}
}
希尔排序
基本思想
希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959 年提出而得名。
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。
代码实现
public void shellSort(int[] array) {
int gap = array.length; // 定义增量
while(gap > 1){
gap /= 2; // 增量的选择
shell(array,gap); // 对该增量下 每个增量序列进行直接插入排序
}
}
public static void shell(int[] array, int gap){
// 外层循环, 对每个增量序列都进行直接直接插入排序
for(int i = gap; i < array.length; i++){
int tmp = array[i]; // 记录拿到的数字
int j = i - gap; // 该数字所在增量序列的前一个数字
// 内层循环, 依次比较前面已排序的增量序列的数字, 找到要插入位置
for( ; j >= 0; j -= gap){
if(array[j] > tmp){
// 该数字大, 往后移动
array[j+gap] = array[j];
}else{
// 找到插入位置, 结束循环
break;
}
}
// 插入该位置
array[j+gap] = tmp;
}
}
直接选择排序
基本思想
直接选择排序(Straight Select Sorting) 也是一种简单的排序方法,它的基本思想是:第一次从R[0]~R[n-1]中选取最小值,与R[0]交换,第二次从R[1]~R[n-1]中选取最小值,与R[1]交换,....,第i次从R[i-1]~R[n-1]中选取最小值,与R[i-1]交换,.....,第n-1次从R[n-2]~R[n-1]中选取最小值,与R[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。
代码实现
public void selectSort(int[] array) {
int n = array.length;
// 外层循环 每次都找到第 i 小的数据并交换
for(int i = 0; i < n; i++){
int minIndex = i; // 记录最小值的下标
// 内层循环 找出 j 位置到最后的最小值下标
for(int j = i+1; j < n; j++){
if(array[j] < array[minIndex]){
// 更新最小值下标
minIndex = j;
}
}
// 将 i 位置元素与 minIndex 位置元素交换
int tmp = array[i];
array[i] = array[minIndex];
array[minIndex] = tmp;
}
}
堆排序
基本思想
堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
代码实现
public void heapSort(int[] array){
createBigHeap(array); // 因为要排升序, 所以建大根堆
int end = array.length-1;
// 大根堆中, 根节点即为最大值 每次将根节点与 end 位置交换, 相当于固定一个最大值, 循环结束
// 数组将变为有序
while(end > 0){
swap(array, 0, end); // 将根节点与最后一个节点互换, 即为把最大的数放后面
shiftDown(array, 0, end); // 将根节点向下调整, 使得 [0,end-1] 成为新的大根堆
end--;
}
}
// 建大根堆
public static void createBigHeap(int[] array){
// (array.length)/2 - 1 即为最后一个非叶子节点
// 从最后一个非叶子节点开始 一直调整到根节点, 数组即为大根堆
for(int parent = (array.length)/2 - 1; parent >= 0; parent--){
shiftDown(array, parent, array.length); // 向下调整
}
}
// 向下调整
public static void shiftDown(int[] array, int parent, int len){
int child = 2*parent + 1; // 记录孩子节点
while(child < len){
// 如果右孩子存在, 找到左右孩子中较大的那个
if(child+1 < len){
if(array[child+1] > array[child]){
child++;
}
}
if(array[child] > array[parent]){
// 双亲节点小于最大的孩子节点 进行交换
swap(array, child, parent);
// 可能会造成子树不满足大根堆的性质,因此需要继续向下调整
parent = child;
child = 2 * parent + 1;
}else {
// 双亲节点大于最大的孩子节点, 结束循环
break;
}
}
}
// 交换数组中两个下标的值
public static void swap(int[] array, int i, int j){
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
冒泡排序
基本思想
冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。
它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行,直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
代码实现
public void bubbleSort(int[] array){
// 外层循环 每次都会将最大的值交换到末尾
for(int i = 0; i < array.length-1; i++){
// 内层循环 从前往后比较 array.length-i 次
// 因为 i 次外层循环已经将最大的数排好, 无需再比较
for(int j = 0; j < array.length-1-i; j++){
if(array[j] > array[j+1]){
// 如果 j 位置元素 大于 j+1 位置元素, 那就交换
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
}
}
}
}
快速排序
基本思想
快速排序采用的是分治思想,即在一个无序的序列中选取一个任意的基准元素pivot,利用pivot将待排序的序列分成两部分,前面部分元素均小于或等于基准元素,后面部分均大于或等于基准元素,然后采用递归的方法分别对前后两部分重复上述操作,直到将无序序列排列成有序序列。
代码实现
public void sortArray(int[] nums) {
quickSort(nums, 0, nums.length-1);
}
public static void quickSort(int[] array, int l, int r){
// 返回条件
if(l >= r){
return;
}
// 1:先找到基准元素
int key = array[new Random().nextInt(r - l + 1) + l];
// 2:对数组进行遍历 划分为三个区间
int left = l-1, right = r+1, cur = l;
// [l,left] 小于 key
// (left,right) 等于 key
// [right,r] 大于 key
while(cur < right){
if(array[cur] < key){
swap(array,cur++,++left);
}else if(array[cur] == key){
cur++;
}else{
swap(array,cur,--right);
}
}
// 3:分别对 [l,left],[right,r] 递归
quickSort(array,l,left);
quickSort(array,right,r);
}
// 交换数组中两个下标的值函数
public static void swap(int[] nums, int i, int j){
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
归并排序
基本思想
归并排序是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
代码实现
int[] tmp; // 定义一个全局变量数组 用于接收每个已排序的递归部分
public void sort(int[] array) {
tmp = new int[array.length]; // 初始化 tmp
mergeSort(array, 0, array.length-1);
}
public void mergeSort(int[] array, int left, int right){
// 返回条件
if(left >= right){
return;
}
// 1: 定义中点,划分区间
int mid = (left + right) / 2;
// 2: 分别对两个区间进行递归排序
mergeSort(array,left,mid);
mergeSort(array,mid+1,right);
// 3: 合并两个有序数组
int cur1 = left, cur2 = mid+1, i = 0;
while(cur1 <= mid && cur2 <= right){
if(array[cur1] <= array[cur2]){
tmp[i++] = array[cur1++];
}else{
tmp[i++] = array[cur2++];
}
}
// 3.1: 处理没有合并完的数组
while(cur1 <= mid){
tmp[i++] = array[cur1++];
}
while(cur2 <= right){
tmp[i++] = array[cur2++];
}
// 4: 将排序好的数组返回给原数组
for(int j = left; j <= right; j++){
array[j] = tmp[j-left];
}
}
后记
这篇博客到这里就结束啦,可是排序算法依然有很多优化的地方,以上代码均为博主现阶段的常用方式。如有错误或不足之处请指正,欢迎大家讨论~