欢迎大家在语雀进行学习交流
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; }