排序算法
排序算法在面试中可以说是必会问到的知识点,所以我们必须得掌握。
排序
冒泡排序
思路:如上图片每一趟从一个元素开始两个相邻的元素依次作比较,如果当前元素大于下一个元素则交换元素依次类推即可。
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,是不会再交换的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
冒泡排序总的平均时间复杂度为O(n^2)
代码实现:
public static void sorted(int[] arr) {
//趟数
for (int i = 0; i < arr.length-1; i++) {
//次数
for (int j = 0;j<arr.length-1-i;j++){
if (arr[j] > arr[j+1]){
//交换数据
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
冒泡排序优化
冒泡排序的优化实际上是加上flag标志,减少了排序比较次数和没必要的遍历。如果添加了flag以后表示falg初值为false,如果他在内层的for循环中一次都没有进行交换数据表明数据有序所以可以终止排序算法了,直接退出for循环。
代码实现:
//冒泡排序的优化
public static void optimizeSorted(int[] arr){
boolean flag = true;
for (int i = 0; i < arr.length && flag; i++) {
flag = false;
for (int j = 0;j<arr.length-1-i;j++){
if (arr[j] > arr[j+1]){
flag = true;
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
选择排序
第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。
public static void selectSort(int[] a) {
for (int i = 0; i < arr.length; i++) {
int min = i;
for (int j = i + 1; j < n; j++) {
if(a[min] > a[j]) min = j;
}
//交换
int temp = a[i];
a[i] = a[min];
a[min] = temp;
}
}
打擂台排序
这里需要注意的是打擂台排序并不是选择排序,我在之前一直以为选择就是打擂台。总结的时候发现并不是。
实现思路:就是固定一个元素然后一直后面的作比较如果当前元素大于下一元素,直接交换值依次类推就可以实现。
代码如下:
public static void ringSort(int[] arr){
for (int i = 0; i < arr.length; i++) {
for (int j = i+1;j < arr.length-1;j++){
if (arr[i] > arr[j]){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
}
插入排序
插入排序的工作方式像许多人排序一手扑克牌。开始时,我们的左手为空并且桌子上的牌面向下。然后,我们每次从桌子上拿走一张牌并将它插入左手中正确的位置。为了找到一张牌的正确位置,我们从右到左将它与已在手中的每张牌进行比较。拿在左手上的牌总是排序好的,原来这些牌是桌子上牌堆中顶部的牌。
时间复杂度:O(n^2)
public static void insertSort(int[] arr){
for (int i = 0; i < arr.length; i++) {
//内层表示已经i之前有序,当前元素进行比较插入
for (int j = i;j > 0;j--){
if (arr[j] < arr[j-1]){
int tmp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = tmp;
}
}
}
}
插入排序优化
使用while循环减少了没必要的循环当被插入元素大于该位置的元素时,当前的位置不变,后面的循环也就不需要执行了直接跳出,时间复杂度降低。
public static void optimizeInsertSort(int[] arr){
for (int i = 0; i < arr.length; i++) {
int j = i;
while(j > 0 && arr[j]<arr[j-1]){
//将arr[j]插入有序数组
int tmp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = tmp;
j--;
}
}
}
希尔排序
希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959 年提出而得名。
实现思路:第一次找数组的一半,第二次就是一半的一半做增量,两两比较如上图所示
public static void shellSort(int[] arr){
for (int h = arr.length/2;h > 0;h /= 2){
for (int i = h;i < arr.length;i++){
for (int j = i;j > h-1;j -= h){
if (arr[j] < arr[j-h]){
int tmp =arr[j];
arr[j] = arr[j-h];
arr[j-h] = tmp;
}
}
}
}
}
当数据量比较大的时候可以使用克努特序列
public static void optimizeShellSort(int[] arr){
int interval = 1;
while (interval <= arr.length/3){
interval = interval*3+1;
}
for (int h = interval;h > 0;h /= 2){
for (int i = h;i < arr.length;i++){
for (int j = i;j > h-1;j -= h){
if (arr[j] < arr[j-h]){
int tmp =arr[j];
arr[j] = arr[j-h];
arr[j-h] = tmp;
}
}
}
}
}
快速排序
快速排序由C. A. R. Hoare在1960年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
代码如下:
private static int count = 0;
public static void quickSort(int[] arr,int start,int end){
if (start < end){
// int index = OptimizeGetIndex(arr,start,end);
int index = getIndex(arr,start,end);
quickSort(arr,start,index-1);
quickSort(arr,index+1,end);
}
} //单向一次扫描
private static int getIndex(int arr[], int start, int end)
{
//最后一个元素为基准
int x = arr[end];
int i = start;
//记录比基准大的元素下标
int j = start;
for (;i < end;i++){
//大于基准后找到后面的比基准小的交换位置
if (arr[i] < x){
//交换i,j指向的元素
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
j++;
count++;
}
}
arr[end] = arr[j];
arr[j] = x;
return j;
}
代码优化的过程有注释:
//快排扫描优化
private static int OptimizeGetIndex(int[] arr, int start, int end) {
int i = start;
int j = end;
int x =arr[i];
while (i < j){
//从右向左找小于基准的
while (i<j&&arr[j]>=x){
j--;
}
//表示找到了跳出了上一层while循环
if (i < j){
arr[i] =arr[j];
count++;
i++;
}
//从左向右找大于基准的
while (i < j && arr[i] < x){
i++;
}
//表示找到了跳出了上一层while循环
if (i < j){
arr[j] = arr[i];
count++;
j--;
}
}
//防止数据重复在基准位置填充被替换的元素
arr[i] = x;
return i;
}
归并排序
归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
实现思路:
代码如下:
private static void split(int[] arr, int start, int end) {
int center = (start+end)/2;
if (start < end){
split(arr,start,center);
split(arr,center+1,end);
merger(arr,start,center,end);
}
}
private static void merger(int[] arr, int start, int center, int end) {
//定义一个临时数组
int[] tmp = new int[end-start+1];
//定义左边数组的起始索引
int i = start;
//右边数组的起始索引
int j = center + 1;
//临时数组的的起始索引
int index = 0;
//比较左右两个数组的大小
while (i <= center && j <= end){
if (arr[i] <= arr[j]){
tmp[index] = arr[i];
i++;
}else {
tmp[index] = arr[j];
j++;
}
index++;
}
//处理剩余元素
while (i <= center){
tmp[index] = arr[i];
i++;
index++;
}
while (j<=end){
tmp[index] = arr[j];
j++;
index++;
}
//改变处理后数组对应原数组的值
for (int k = 0;k <tmp.length;k++){
arr[k+start] = tmp[k];
}
}
堆排序
堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
堆排序可分为:大顶堆、小顶堆
大顶堆:父节点永远大于他的子节点
从第一个非叶子节点开始向上转换
小顶堆:父节点永远小于他的子节点
这张图片是网上
代码如下:
private static void heapSort(int[] arr){
//拿到第一个非叶子节点
int startIndex =(arr.length-1)/2;
for (int i = startIndex;i >= 0;i--){
toHeap(arr,arr.length,i);
}
//将最后一个元素与之第一个元素调换,更新数组状态
for (int i = arr.length-1;i > 0;i--){
int tmp = arr[i];
arr[i] = arr[0];
arr[0] = tmp;
//每一次找到大根堆的根点与之最后一个节点交换传入i的值表示要处理的元素个数
toHeap(arr,i,0);
}
System.out.println(Arrays.toString(arr));
}
/**
* 向下调整
* @param arr 节点数组
* @param length 调换的个数
* @param index 调换的那个节点
*/
private static void toHeap(int[] arr, int length, int index) {
int maxIndex = index;
int leftChild = index*2+1;
int rightChild = index*2+2;
//表示孩子节点元素大于父节点元素
if (leftChild < length && arr[leftChild] > arr[maxIndex]){
maxIndex = leftChild;
}
if (rightChild < length && arr[rightChild] > arr[maxIndex]){
maxIndex = rightChild;
}
//表示存在孩子节点大于父节点进行元素调换
if (maxIndex != index){
int tmp = arr[index];
arr[index] = arr[maxIndex];
arr[maxIndex] = tmp;
//递归调用当前的函数,调换完数据以后,可能堆的稳定性变化了
toHeap(arr,length,maxIndex);
}
}
计数排序
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
实现思路:
代码如下:
private static void cardinalitySort(int[] arr) {
int[][] tmp = new int[10][arr.length];
int max = getMax(arr);
//用来记录对应位置每个桶中元素的索引位置
int[] count = new int[10];
//确定轮次
int len = String.valueOf(max).length();
for (int i = 0,n = 1;i < len;i++,n*=10){
//放入桶中
for (int j = 0; j < arr.length; j++) {
//获取每个位数上的数字
int card =arr[j]/n%10;
tmp[card][count[card]++] = arr[j];
}
//存放时依次放入原数组的下标
int index = 0;
//按顺序取出数据放入依次放入原数组
for (int j = 0;j < count.length;j++){
if (count[j] != 0){
for (int k = 0;k < count[j];k++){
arr[index] = tmp[j][k];
index++;
}
//取出当前桶中的元素,置为0
count[j] = 0;
}
}
}
}
//找到最大元素
private static int getMax(int[] arr) {
int max = arr[0];
for (int i = 1;i < arr.length;i++){
if (arr[i] > max){
max = arr[i];
}
}
return max;
}
点个赞呗~