欢迎大家在语雀进行学习交流
1、插入排序
直接插入排序
- 基本思想:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。
- 时间复杂度为:O(n2)

如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
public void InsertSort(int a[], int n){
for(int i = 1; i < n; i++){
if(a[i] < a[i-1]){ //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入
int j = i-1;
int x = a[i]; //复制为哨兵,即存储待排序元素
a[i] = a[i-1]; //先后移一个元素
while(x < a[j]){ //查找在有序表的插入位置
a[j+1] = a[j];
j--; //元素后移
}
a[j+1] = x; //插入到正确位置
}
}
}
希尔排序
- 算法实现:先将要排序的一组记录按增量d(d=n/2,n为要排序数的个数)分成若干组子序列,每组中记录的下标相差d。对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。继续不断缩小增量直至为1,最后使用直接插入排序完成排序。不稳定的排序

/**
*直接插入排序的一般形式
*@param int dk 缩小增量,如果是直接插入排序dk=1
*/
public void ShellInsertSort(int a[], int n, int dk){
for(int i= dk; i<n; ++i){
if(a[i] < a[i-dk]){ //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入
int j = i-dk;
int x = a[i]; //复制为哨兵,即存储待排序元素
a[i] = a[i-dk]; //首先后移一个元素
while(x < a[j]){ //查找在有序表的插入位置
a[j+dk] = a[j];
j -= dk; //元素后移
}
a[j+dk] = x; //插入到正确位置
}
}
}
//先按照增量d(d=n/2,n为要排序数的个数)进行希尔排序
public void shellSort(int a[], int n){
int dk = n/2;
while( dk >= 1 ){
this.ShellInsertSort(a, n, dk);
dk = dk/2;
}
}
2、选择排序
简单选择排序
- 算法思想:在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。

//查找数组中第i个元素到最后一个元素中的最小值
public int SelectMinKey(int a[], int i){
int k = i;
for(int j=i+1; j < a.length; j++) {
if(a[k] > a[j])
k = j;
}
return k;
}
//选择排序
public void selectSort(int a[]){
int key, tmp;
for(int i = 0; i < a.length; i++) {
key = SelectMinKey(a, i); //选择最小的元素
if(key != i){
tmp = a[i];
a[i] = a[key];
a[key] = tmp; //最小元素与第i位置元素互换
}
}
}
堆排序
- 推荐博客
- 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
-
- 大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
- 小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

如图所示为映射到数组中的样子
- 算法思路
-
- 将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
- 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
- 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
public static void sort(int []arr){
//1.构建大顶堆
for(int i=arr.length/2-1; i >= 0; i--){
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr,i,arr.length);
}
//2.调整堆结构+交换堆顶元素与末尾元素
for(int j=arr.length-1;j>0;j--){
//将堆顶元素与末尾元素进行交换
int temp=arr[a];
arr[a] = arr[b];
arr[b] = temp;
adjustHeap(arr,0,j);//重新对堆进行调整
}
}
/**
* 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
* @param arr
* @param i
* @param length
*/
public static void adjustHeap(int []arr,int i,int length){
int temp = arr[i]; //先取出当前元素i
for(int k=i*2+1; k < length; k=k*2+1){ //从i结点的左子结点开始,也就是2i+1处开始
if(k+1<length && arr[k]<arr[k+1]) //如果左子结点小于右子结点,k指向右子结点
k++;
if(arr[k] > temp){ //如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
arr[i] = arr[k];
i = k;
}else
break;
}
arr[i] = temp; //将temp值放到最终的位置
}
3、交换排序
冒泡排序
- 算法思路:
-
- 将序列当中的左右元素,依次比较,保证右边的元素始终大于左边的元素;( 第一轮结束后,序列最后一个元素一定是当前序列的最大值)
- 对序列当中剩下的n-1个元素再次执行步骤1。
- 对于长度为n的序列,一共需要执行n-1轮比较。
如图为第一轮起泡
// n为数组的长度
public void bubbleSort(int a[], int n){
for(int i = 0 ; i < n-1; i++) {
for(int j = 0; j < n-i-1; ++j) {
if(a[j] > a[j+1]){
int tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp;
}
}
}
}
快速排序
- 算法思想
-
- 从序列当中选择一个基准数(temp),在这里我们选择序列当中第一个数最为基准数
- 将序列当中的所有数依次遍历,比基准数大的位于其右侧,比基准数小的位于其左侧
- 重复步骤1.2,直到所有子集当中只有一个元素为止。

public void isPalindrome(int[] arr, int begin, int end) {
if(begin < end){ //区间中不是只有一个数
int temp = arr[begin]; //取区间中第一个数为基准数
int i = begin; //从左到右查的指针
int j = end; //从右到左查的指针
while(i < j){
while(i < j && arr[j] > temp) //右边的数大于基准数时,不进行操作,继续向左查找
j--;
arr[i] = arr[j]; //将右边小于等于基准元素的数填入右边相应位置
while(i < j && arr[i] <= temp) //左边数小于等于基准数时,不进行操作,继续向右查找
i++;
arr[j] = arr[i]; //将左边大于基准元素的数填入左边相应位置
}
arr[i] = temp; //将基准元素填入相应位置
isPalindrome(arr, begin, i-1); //对基准元素的左边子区间进行快速排序
isPalindrome(arr, i+1, end); //对基准元素的右边子区间进行快速排序
}else
return;
}
4、归并排序
- 推荐博客
- 基本思想:归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

//排序是直接调用的方法
public void sort(int[] arr){
//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
int[] temp = new int[arr.length];
sort(arr, 0, arr.length-1, temp);
}
//将数组分成小数组,并调用合并排序方法
private void sort(int[] arr, int left, int right, int[] temp){
if(left < right){
int mid = (left+right)/2;
sort(arr, left, mid, temp); //左边归并排序,使得左子序列有序
sort(arr, mid+1, right, temp); //右边归并排序,使得右子序列有序
merge(arr, left, mid, right, temp); //将两个有序子数组合并操作
}
}
//对数组进行合并排序,并将排序好的数组放回原数组arr中
private void merge(int[] arr, int left, int mid, int right, int[] temp){
int i = left; //左序列指针
int j = mid+1; //右序列指针
int t = 0; //临时数组指针
while (i<=mid && j<=right){
if(arr[i] <= arr[j]){
temp[t++] = arr[i++];
}else {
temp[t++] = arr[j++];
}
}
while(i<=mid){ //将左边剩余元素填充进temp中
temp[t++] = arr[i++];
}
while(j<=right){ //将右序列剩余元素填充进temp中
temp[t++] = arr[j++];
}
//将temp中排好序的元素全部拷贝到原数组中
t = 0;
while(left <= right){
arr[left++] = temp[t++];
}
}
5、利用桶的概念的排序
差异
- 基数排序:根据键值的每位数字来分配桶;
- 计数排序:每个桶只存储单一键值;
- 桶排序:每个桶存储一定范围的数值;
计数排序
- 算法步骤:
-
- 找出待排序的数组中最大和最小的元素
- 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
- 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
- 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
- 特征:
-
- 当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 Θ(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序中的算法来排序数据范围很大的数组。

@Override
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
int maxValue = getMaxValue(arr);
return countingSort(arr, maxValue);
}
private int[] countingSort(int[] arr, int maxValue) {
int bucketLen = maxValue + 1;
int[] bucket = new int[bucketLen];
for (int value : arr) {
bucket[value]++;
}
int sortedIndex = 0;
for (int j = 0; j < bucketLen; j++) {
while (bucket[j] > 0) {
arr[sortedIndex++] = j;
bucket[j]--;
}
}
return arr;
}
private int getMaxValue(int[] arr) {
int maxValue = arr[0];
for (int value : arr) {
if (maxValue < value) {
maxValue = value;
}
}
return maxValue;
}
桶排序
- 桶排序:是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要
- 为了使桶排序更加高效,我们需要做到这两点:
-
- 在额外空间充足的情况下,尽量增大桶的数量
- 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中
- 到输入的元素可以均匀的分配到每一个桶中时最快,当输入的元素分配到一个桶中时最慢

private static final InsertSort insertSort = new InsertSort();
@Override
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
return bucketSort(arr, 5);
}
private int[] bucketSort(int[] arr, int bucketSize) throws Exception {
if (arr.length == 0) {
return arr;
}
//获取数组中的最小值和最大值
int minValue = arr[0];
int maxValue = arr[0];
for (int value : arr) {
if (value < minValue) {
minValue = value;
} else if (value > maxValue) {
maxValue = value;
}
}
//返回小于等于x的最大整数:Math.floor(x);
int bucketCount = (int) Math.floor((maxValue - minValue) / bucketSize) + 1;
int[][] buckets = new int[bucketCount][0];
// 利用映射函数将数据分配到各个桶中
for (int i = 0; i < arr.length; i++) {
int index = (int) Math.floor((arr[i] - minValue) / bucketSize);
buckets[index] = arrAppend(buckets[index], arr[i]);
}
int arrIndex = 0;
for (int[] bucket : buckets) {
if (bucket.length <= 0) {
continue;
}
// 对每个桶进行排序,这里使用了插入排序
bucket = insertSort.sort(bucket);
for (int value : bucket) {
arr[arrIndex++] = value;
}
}
return arr;
}
//自动扩容,并保存数据
private int[] arrAppend(int[] arr, int value) {
arr = Arrays.copyOf(arr, arr.length + 1);
arr[arr.length - 1] = value;
return arr;
}
基数排序
- 算法原理:将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数

如果最大数有更多位可继续进行比较
@Override
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
int maxDigit = getMaxDigit(arr);
return radixSort(arr, maxDigit);
}
//获取最高位数
private int getMaxDigit(int[] arr) {
int maxValue = getMaxValue(arr);
return getNumLenght(maxValue);
}
//获取最大值
private int getMaxValue(int[] arr) {
int maxValue = arr[0];
for (int value : arr) {
if (maxValue < value) {
maxValue = value;
}
}
return maxValue;
}
//获取数字的位数
protected int getNumLenght(long num) {
if (num == 0) {
return 1;
}
int lenght = 0;
for (long temp = num; temp != 0; temp /= 10) {
lenght++;
}
return lenght;
}
private int[] radixSort(int[] arr, int maxDigit) {
int mod = 10;
int dev = 1;
for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
// 考虑负数的情况,这里扩展一倍队列数,其中 [0-9]对应负数,[10-19]对应正数 (bucket + 10)
int[][] counter = new int[mod * 2][0];
for (int j = 0; j < arr.length; j++) {
int bucket = ((arr[j] % mod) / dev) + mod;
counter[bucket] = arrayAppend(counter[bucket], arr[j]);
}
int pos = 0;
for (int[] bucket : counter) {
for (int value : bucket) {
arr[pos++] = value;
}
}
}
return arr;
}
/**
* 自动扩容,并保存数据
*
* @param arr
* @param value
*/
private int[] arrayAppend(int[] arr, int value) {
arr = Arrays.copyOf(arr, arr.length + 1);
arr[arr.length - 1] = value;
return arr;
}
本文深入讲解了各种排序算法,包括插入排序、选择排序、交换排序、归并排序、基数排序等,详细阐述了每种算法的基本思想、实现方式及时间复杂度。
3430

被折叠的 条评论
为什么被折叠?



