一. 排序的概念及应用
1. 排序的概念
- **排序 :**所谓排序就是 , 使一串记录 , 按照其中的某个或某个关键字的大小 , 递增或者递减的排列起来的操作
- 稳定性 : 假定在待排序的记录序列中 , 存在多个具有相同的关键字的记录 , 若经过排序 , 这些记录的相对次序保持不变 , 即在原序列中 , r[i] = r[j] , 且 r[i] 在 r[j] 之前 , 而经过排序后的序列 r[i] 仍然在 r[j] 之前 , 则称这种算法是稳定的 , 否则为不稳定的

- 内部排序 : 数据元素全放在内存中排序
- 外部排序 : 数据元素太多不能同时放在内存上 , 根据排序过程的要求在 内外存之间移动数据的排序
2. 排序的运用
排序是计算机科学、数据处理及日常生活中核心的基础操作,其本质是按照预设规则(如大小、时间、优先级等)对一组无序数据进行重新组织,最终形成有序序列。它的核心价值在于降低数据查找、分析和利用的成本,广泛应用于系统开发、数据分析、日常工具等多个领域
3. 常见的排序算法

(本文将其局部实现)
二.常见排序算法的实现(测试部分集中写)
1. 插入排序
把待排序的记录按照其关键码值的大小 逐个插入到一个已经排好序的有序序列中 , 直到 所有的记录插入完为止 , 得到一个新的有序序列
1.1 直接插入排序
当插入第 i 个元素时 , 前面的 0~i-1 下标的元素已经排序好了 , 此时 用 i 下标的元素与前 0~i-1 下标的元素进行比较 , 找到插入的位置将 i 下标的元素插入 , 原来位置上的元素向后移动
代码示例 :
java
/**直接插入排序
* 时间复杂度 : O(N2)
* 空间复杂度 : O(1)
* 稳定性 : 稳定
*/
public static void insetSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int j = i - 1;
int tmp = arr[i];
while (j >= 0) {
if (arr[j] > tmp) {
arr[j + 1] = arr[j];
j--;
} else {
break;
}
}
arr[j + 1] = tmp;
}
}
直接插入排序的特性总结 :
- 步骤 : 每次 将 tmp 元素插入到有序序列中 , 第一轮循环认为一个元素是有序的
- 元素越接近有序 , 直接插入排序算法的时间效率越高
- 时间复杂度 : O(N^2)
- 空间复杂度: O(1)
- 稳定性 : 稳定
1.2 希尔排序
- 希尔排序又称缩小增量法 . 希尔排序的基本思想是 : 先选定一个整数 , 把待排序的文件中所有记录分成多个组 , 所有距离为指定记录分在同一组中 , 并对每一组内的记录进行排序 . 然后然后逐步缩小这个整数 (增量),重复上述分组和排序的操作,当增量缩小到 1 时,整个文件就被分成一组,此时进行最后一次排序,排序完成
- 希尔排序的核心在于通过不断缩小增量,让数据先进行宏观上的调整(远距离元素交换),逐渐消除数据中的 "逆序对",当增量为 1 时,实际上就是执行一次插入排序,但此时数据已经基本有序,插入排序能高效完成最后的微调,从而整体提升排序效率
- 相比直接插入排序,希尔排序通过这种 "跳跃式 " 的分组排序,减少了元素移动的次数 ,尤其在处理大规模数据时,效率有明显提升

代码示例 :
java
/**希尔排序
* 时间复杂度 : O(n^1.3) - O(n^1.5)
* 空间复杂度 : O(1)
* 稳定性 : 不稳定
*/
public static void shellSort(int[] array){
int gap = array.length;
while (gap>1){
gap = gap/3+1;
insetSortByShell(array,gap);
}
}
public static void insetSortByShell(int[] array,int gap){
for (int i = 1; i < array.length; i++){
int j = i-gap;
int tmp = array[i];
for (;j>=0;j-=gap){
if(array[j]>tmp){
array[j+gap] = array[j];
}else {
break;
}
}
array[j+gap] = tmp;
}
}
希尔排序的特性总结 :
- 希尔排序是对直接排序的优化
- 当 gap > 1 时 , 都是预排序 , 目的是让数组更接近于有序
- 当 gap = 1 时 , 数组已经有序了 , 这样就会很快
- 注意 : 希尔排序的时间复杂度不好计算 , 因为 gap 的取值有很多种 , 导致很难去计算 , 因此在计算希尔排序的时间复杂度时 , 都不固定
- gap 的取法 : 一般取 gap = len/2 , gap = gap/2 , 直到 gap 为 1 或者取 gap = gap/3+1.
- 时间复杂度 : O(n^1.3) - O(n^1.5)
- 空间复杂度 : O(1)
- 稳定性 : 不稳定
2.选择排序
每一次从待排序的数据元素中 选出最小值 (或最大值) 的一个元素 , 存放在序列的起始位置 , 直到全部待排序的数据元素排完
2.1 直接选择排序
- 在下标 i~n-1 中选择关键码最小(或最大)的数据元素
- 若不是这组元素中的第一个元素 , 则将它与这组元素中的第一个元素互换
- 在剩余 i+1~n-1 中 , 重复上述操作
代码示例 :
java
/**选择排序
* 时间复杂度 : O(N^2)
* 空间复杂度 : O(1)
* 稳定性 : 不稳定
*/
public static void selectSort(int[] array){
for(int i = 0;i<array.length;i++){
int minIndex = i;
for(int j = i+1;j<array.length;j++){
if(array[j]<array[minIndex]){
minIndex = j;//只更新索引 , 先不进行交换
}
}
swap(array,i,minIndex);
}
public static void swap(int[] array,int i,int j){
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
直接选择排序的特性总结 :
- 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:不稳定
选择遍历Pro
java
/**选择排序
* 时间复杂度 : O(N^2)
* 空间复杂度 : O(1)
* 稳定性 : 不稳定
*/
public static void swap(int[] array,int i,int j){
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
public static void selectSortPro(int[] array){
int left = 0;
int right = array.length-1;
while (left<right){
int minIndex = left;
int maxIndex = right;
for (int i = left; i <= right ; i++) {
//要确保当前遍历的元素包含left,right下标的元素,
// 避免出现最开始left存储最大值下标,
//right存储最小值下标 ,而遍历时 ,不能遍历到
if(array[i]<array[minIndex]){
minIndex = i;
}
if(array[i]>array[maxIndex]){
maxIndex = i;
}
}
swap(array,minIndex,left);.
////最大值正好是 left下标 此时 把最大值换到了minIndex的位置了
if(maxIndex == left) {
maxIndex = minIndex;//更新索引
}
swap(array,right,maxIndex);
left++;
right--;
}
}
2.2 堆排序
- 堆排序是指利用 堆积树(堆)这种数据结构(完全二叉树)所设计的一种排序算法 , 它是选择排序的一种
- 通过 构建大根堆 和 交换堆顶与末尾元素 , 反复筛选出最大值并放到数组末尾 , 从而实现排序
- 注意 : 排升序要建立大根堆 , 排降序要建立小根堆
代码示例 :
java
/**堆排序(大根堆 向下调整)
* 时间复杂度 :O(N*logN)
* 空间复杂度 :O(1)
* 稳定性 :不稳定
*/
//堆排序的主方法
public static void heapSort(int[] array){
creatHeap(array);
int end = array.length-1;
while (end>0){
swap(array,0,end);
siftDown(array,0,end);
end--;
}
}
//构建初始堆
public static void creatHeap(int[] array){
for(int parent = (array.length-1-1)/2;parent>=0;parent--){
siftDown(array,parent,array.length);
}
}
//向下调整堆
public static void siftDown(int[] array,int parent,int len){
int child = parent*2+1;
while(child<len) {
if (child + 1 < len && array[child] < array[child + 1]) {
child++;
}
if (array[parent] < array[child]) {
swap(array, parent, child);
parent = child;
child = parent * 2 + 1;
} else {
break;//父节点已经大于等于子节点 , 退出循环
}
}
}
//交换方法
public static void swap(int[] array,int i,int j){
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
堆排序的特性总结 :
- 堆排序使用堆来选数 , 效率搞了很多
- 时间复杂度 : O(NlogN)
- 空间复杂度 : O(1)
- 稳定性 : 不稳定
3.交换排序
就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置 , 交换排序的特点是 : 将键值加大的记录向序列的尾部移动 , 键值较小的记录向序列的前部移动
3.1 冒泡排序
- 相邻元素两两比较,将较大的元素逐步 "冒泡" 到数组末尾
- 每轮排序后,最大的元素会就位,因此下一轮可以减少一次比较
- 增加了交换标记,如果某轮没有发生交换,说明数组已经有序,可提前结束排序
代码示例 :
java
public static void bubbleSort(int[] array){
for (int i = 0; i < array.length-1; i++) {
boolean flag = true;
for(int j = 0;j<array.length-1-i;j++){
if(array[j]>array[j+1]){
flag = false;
swap(array,j,j+1);
}
}
if(flag == true){
break;
}
}
}
冒泡排序的特性总结 :
- 时间复杂度 : O (n²),但在数组接近有序时,优化后的版本性能会更好
- 空间复杂度 : O(1)
- 稳定性 : 稳定
3.2 快速排序
快速排序是 Hoare 于 1962 年提出的一种二叉树结构的交换排序算法
思路 : 任取待排序元素序列中的某元素作为基准值 , 按照该排序码将 待排序集合分割为两子序列 , 左子序列 中所有元素小于基准值 , 有子序列中所有元素军大于基准值 , 然后最左右子序列重复该过程 , 直到所有元素都排列在相应位置上为止
代码示例 :
java
/**
* 快速排序
* 时间复杂度 :O(N*logN)
* 空间复杂度 : O(logN)
* 稳定性 :不稳定
*/
public static void quickSort(int[] array){//为了和前面的排序统一接口
quick(array,0,array.length-1);
}
public static void quick(int[] array,int start,int end){
if(start>=end){//①start==end时,表示这个区间只有这一个元素;
//②start>end 时,表示这个区间是一个空的,没有继续递归的必要了
//就是 基准值在区间的最右边时,再去递归时发现,start为pivot+1,end为end,此时start>end
return;
}
int pivot = partition(array,start,end);//令第一个值为基准值 , 经过partitoin()方法 , 将基准值放到指定位置;
//即 : 基准值的左边都小于基准值 , 基准值的右边都大于基准值
quick(array,start,pivot-1);//递归基准值的左边
quick(array,pivot+1,end);//递归基准值的右边
}
java
//挖坑法
private static int partition(int[] array,int left,int right){
int tmp = array[left];
int l = left;//不直接对形参作变化
int r = right;
while(l<r){
while (l<r&&array[r]>=tmp){//从后边找 比基准值小的;遇到比基准值大的就跳过
r--;
}
array[l] = array[r];
while (l<r&&array[l]<=tmp){//从前面找 比基准值大的;遇到比基准值小的就跳过
l++;
}
array[r] = array[l];
}
array[l] = tmp;
return l;
}
java
//前后指针法(了解)
private static int partition2(int[] array, int left, int right) {
int prev = left ;
int cur = left+1;
while (cur <= right) {
if(array[cur] < array[left] && array[++prev] != array[cur]) {
swap(array,cur,prev);
}
cur++;
}
swap(array,prev,left);
return prev;
}
java
//hoare版
private static int partitoinByHoare(int[] array,int left,int right) {
int tmp = array[left];//令这个区间上的第一个元素时基准值;下面操作是要将基准值复位
int l = left;//不直接对形参作变化
int r = right;
while(l<r){
while (l<r&&array[r]>=tmp){//从后边找 比基准值小的;遇到比基准值大的就跳过
r--;
}
while (l<r&&array[l]<=tmp){//从前面找 比基准值大的;遇到比基准值小的就跳过
l++;
}
swap(array,l,r);//此时交换元素
}
swap(array,left,l);//此时:基准值左边的所有元素比基准值小,基准值右边的所有元素比基准值大
return l;
}
① 方法三种的问题 : 为什么不能先从前往后找比基准值小的呢 ?
答: 如果先从后往前找比基准值小的元素 , 后找大于基准值的元素 , 则最后交换元素时 , left 和 right 会在大于基准值的位置相遇 ; 此时如果直接交换元素 , 则不能满足基准位置前比基准值小 , 基准值后比基准值大 ; 若先从后往前找则不会出现该问题
② 方法三种比较元素时为什么采用大于等于 ?
答 : 取等可以避免开始时 left 和 right 下标的值相同 , 都不满足比基准值大和小 , 则 r 和 l 都不会接着调整下标 , 导致死循环
优化快速排序 :
java
//快速排序整体优化
public static void quickSortNew(int[] array) {//为了和前面的排序统一接口
quickNew(array, 0, array.length - 1);
}
public static void quickNew(int[] array, int start, int end) {
if (start >= end) {
return;
}
int middle = getMiddle(array, start, end);
swap(array, start, middle);
int pivot = partition(array, start, end);
quickNew(array, start, pivot - 1);//递归基准值的左边
quickNew(array, pivot + 1, end);//递归基准值的右边
}
private static int getMiddle(int[] array, int left, int right) {
int middle = (left + right) / 2;
if (array[left] < array[right]) {
if (array[middle] < array[left]) {
return left;
}
if (array[right] < array[middle]) {
return right;
} else {
return middle;
}
} else {//array[left]>=array[right];前提
if (array[middle] > array[left]) {
return left;
}
if (array[right] > array[middle]) {
return right;
} else {
return middle;
}
}
}
//挖坑法
private static int partition(int[] array,int left,int right){
int tmp = array[left];
int l = left;//不直接对形参作变化
int r = right;
while(l<r){
while (l<r&&array[r]>=tmp){//从后边找 比基准值小的;遇到比基准值大的就跳过
r--;
}
array[l] = array[r];
while (l<r&&array[l]<=tmp){//从前面找 比基准值大的;遇到比基准值小的就跳过
l++;
}
array[r] = array[l];
}
array[l] = tmp;
return l;
}
快速排序非递归 :
java
public static void quickSort(int[] array) {
quickNor(array,0,array.length-1);
}
private static int partition(int[] array, int left, int right) {
int tmp = array[left];
int l = left;
int r = right;
while (l < r) {
while (l < r && array[r] >= tmp) {
}
array[l] = array[r];
while (l < r && array[l] <= tmp) {
}
array[r] = array[l];
}
array[l] = tmp;
return l;
}
public static void quickNor(int[] array,int start,int end) {
Deque<Integer> stack = new ArrayDeque<Integer>();
int pivot = partition(array,start,end);
if(pivot>start+1){
stack.push(start);
stack.push(pivot-1);
}
if(pivot<end-1){
stack.push(pivot+1);
stack.push(end);
}
while (!stack.isEmpty()){
end = stack.pop();
start = stack.pop();
pivot = partition(array,start,end);
if(pivot>start+1){
stack.push(start);
stack.push(pivot-1);
}
if(pivot<end-1){
stack.push(pivot+1);
stack.push(end);
}
}
}
快速排序的特性总结 :
- 时间复杂度 : O(N*logN)
- 空间复杂度 : O(logN)
- 稳定性 : 不稳定
4. 归并排序
- 归并排序是建立在归并操作上的 一种有效的排序算法 , 该算法是采用的 分治法 的一个非常典型的应用
- 将已有序的子列进行合并 , 得到完全有序的序列 ; 即 先使每个子序列有序 , 再使 子序列段 间有序
- 若将两个有序表合并成一个有序表 , 称为二路归并
代码示例 :
java
/**
* 归并排序
* 时间复杂度 : O(N*logN)
* 空间复杂度 : O(N)
* 稳定性 : 稳定
*/
public static void mergeSort(int[] array){
mergeSortTmp(array,0,array.length-1);
}
private static void mergeSortTmp(int[] array, int left,int right) {
if(left>=right){
return;
}
int mid = (left+right)/2;
mergeSortTmp(array,left,mid);
mergeSortTmp(array,mid+1,right);
//分解完成
//合并
merge(array,left,mid,right);
}
private static void merge(int[] array, int left, int mid, int right) {
int[] tmp = new int[right-left+1];
int s1 = left;
int e1 = mid;
int s2 = mid+1;
int e2 = right;
int k = 0;
while (s1<=e1 && s2<=e2){
if(array[s1]>=array[s2]){//放第一段的元素
tmp[k++] = array[s2++];
}else {
tmp[k++] = array[s1++];
}
}
while(s1<=e1){
tmp[k++] = array[s1++];
}
while(s2<=e2){
tmp[k++] = array[s2++];
}
for(int i = 0;i<k;i++){//拷贝数组
array[i+left] = tmp[i];
}
}
步骤 :
- ①分治(分解):
- mergeSort 方法是归并排序的入口,调用 mergeSortTmp 方法从数组的起始位置(0)到末尾位置(array.length - 1)开始排序
- mergeSortTmp 方法通过递归将数组不断分成左右两个子数组,直到子数组长度为 1(即 left >= right 时递归终止)。例如,对于数组 [5, 3, 8, 6, 2],会先分解为 [5]、[3]、[8]、[6]、[2] 这些长度为 1 的子数组
- ②合并:
- 当左右子数组都被递归排序后,调用 merge 方法将它们合并成一个有序的数组。
- 在 merge 方法中,创建临时数组 tmp 来存储合并后的结果
- 使用两个指针 s1(指向左子数组起始位置)、e1(指向左子数组末尾位置)和 s2(指向右子数组起始位置)、e2(指向右子数组末尾位置),比较两个子数组中的元素,将较小的元素依次放入临时数组 tmp 中
- 当其中一个子数组的元素全部放入 tmp 后,将另一个子数组剩余的元素依次放入 tmp
- 最后将 tmp 中的元素拷贝回原数组的对应位置
归并排序非递归 :
java
/**
* 非递归实现 归并排序
*/
public static void mergeSortNor(int[] array) {
int gap = 1;
while (gap < array.length) {
for (int i = 0; i < array.length; i = i + gap * 2) {
int left = i;
int mid = left + gap - 1;
if(mid >= array.length) {
mid = array.length-1;
}
int right = mid + gap;
if(right >= array.length) {
right = array.length-1;
}
mergeN(array,left,mid,right);
}
gap *= 2;
}
}
private static void mergeN(int[] array, int left, int mid, int right) {
int[] tmp = new int[right-left+1];
int s1 = left;
int e1 = mid;
int s2 = mid+1;
int e2 = right;
int k = 0;
while (s1<=e1 && s2<=e2){
if(array[s1]>=array[s2]){//放第一段的元素
tmp[k++] = array[s2++];
}else {
tmp[k++] = array[s1++];
}
}
while(s1<=e1){
tmp[k++] = array[s1++];
}
while(s2<=e2){
tmp[k++] = array[s2++];
}
for(int i = 0;i<k;i++){//拷贝数组
array[i+left] = tmp[i];
}
}
归并排序的特性总结 :
- 归并排序的缺点在于 需要 O(N)的空间复杂度 , 归并排序的思考更多的是 解决在磁盘中的外排问题
- 时间复杂度 : O(N*logN)
- 空间复杂度 : O(N)
- 稳定性 : 稳定
三.排序测试
代码示例 :
java
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Random;
public class Test {
public static void testInsert(int[] array) {//计算时间
array = Arrays.copyOf(array,array.length);
long startTime = System.currentTimeMillis();
TestSort.insetSort(array);
long endTime = System.currentTimeMillis();
System.out.println("直接插入排序耗时:"+(endTime-startTime));
}
public static void testshellSort(int[] array) {//计算时间
array = Arrays.copyOf(array,array.length);
long startTime = System.currentTimeMillis();
TestSort.shellSort(array);
long endTime = System.currentTimeMillis();
System.out.println("希尔排序耗时:"+(endTime-startTime));
}
public static void testselectSort(int[] array) {//计算时间
array = Arrays.copyOf(array,array.length);
long startTime = System.currentTimeMillis();
//TestSort.selectSort(array);
TestSort.selectSortPro(array);
long endTime = System.currentTimeMillis();
System.out.println("选择排序耗时:"+(endTime-startTime));
}
public static void testheapSort(int[] array) {//计算时间
array = Arrays.copyOf(array,array.length);
long startTime = System.currentTimeMillis();
TestSort.heapSort(array);
long endTime = System.currentTimeMillis();
System.out.println("堆排序排序耗时:"+(endTime-startTime));
}
public static void testbubbleSort(int[] array) {//计算时间
array = Arrays.copyOf(array,array.length);
long startTime = System.currentTimeMillis();
TestSort.bubbleSort(array);
long endTime = System.currentTimeMillis();
System.out.println("冒泡排序排序耗时:"+(endTime-startTime));
}
public static void testquickSort(int[] array) {//计算时间
array = Arrays.copyOf(array,array.length);
long startTime = System.currentTimeMillis();
TestSort.quickSort(array);
long endTime = System.currentTimeMillis();
System.out.println("快速排序排序耗时:"+(endTime-startTime));
}
public static void testmergeSort(int[] array) {//计算时间
array = Arrays.copyOf(array,array.length);
long startTime = System.currentTimeMillis();
TestSort.mergeSort(array);
//TestSort.mergeSortNor(array);
long endTime = System.currentTimeMillis();
System.out.println("归并排序排序耗时:"+(endTime-startTime));
}
public static void testSimple(){
int[] array = {6,4,20,15,7,8,55,6,14,88,2};
System.out.println("排序前"+Arrays.toString(array));
//TestSort.insetSortClass(array);
//TestSort.shellSort(array);
//TestSort.selectSort(array);
//TestSort.selectSortPro(array);
//TestSort.heapSort(array);
//TestSort.bubbleSort(array);
//TestSort.quickSort(array);
//TestSort.mergeSort(array);
TestSort.mergeSortNor(array);
System.out.println("排序后"+Arrays.toString(array));
}
public static void testother(){
int[] array = new int[10000];
NorOrderArray(array);
//orderArray(array);
testInsert(array);
testshellSort(array);
testselectSort(array);
testheapSort(array);
testbubbleSort(array);
testquickSort(array);
testmergeSort(array);
}
public static void main(String[] args) {
testSimple();
//testother();
}
public static void orderArray(int[] array){
for (int i = 0; i < array.length; i++) {
array[i] = i;//顺序
//array[i] = array.length-1-i;//倒序
}
}
public static void NorOrderArray(int[] array){//生成随机数
Random random = new Random();
for (int i = 0; i < array.length; i++) {
array[i] = random.nextInt(10_0000);
}
}
}
四.排序算法复杂度及稳定性分析

