经典排序算法
前言:前段时间面试官问我懂哪些排序算法,我随口就说冒泡选择,他问为什么,我说因为很简单,瞬间我和他都沉默了。再后来问的什么二叉树什么数据结构之类的,我让他直接跳过了,我说出学校这么久了,工作中没用到的,基本都忘了。结果可想而知。所以最近我都在重拾算法数据结构一类知识。
前两周空闲时间都会研究排序算法,基本上每天都会手写一遍,忘了再写写了再忘,工作中遇到的排序问题,我都会刻意的使用一些我不常用的算法,以此来加深记忆。今天写的六种算法,是这几天我学到的,为以后忘了再来看一遍。
1.冒泡:
冒泡的中心思想是两两对比,如果前面一个大于后面一个则交换位置,不断重复这个过程arr.length-1遍后,排序完成。其中要注意的点是:n个元素,其实只需要对比n-1遍即可;内层循环每次都会减少对比i位,因为后面的元素是已经排序好的,不必再次对比。下面是代码:
static void BubbleSort(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 temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } }
2.选择:
选择的中心思想是,每次循环时选择一位元素假设是最小元素,然后在向后遍历的过程中,如果选择的最小元素比后面的元素大,则最小元素改变。其中要注意的点是:内层循环的下标应该是比外层循环的下标大一个的,总不会自己和自己比吧?内层循环结束后,添加判断min是否变化了,如果变化了则交换,否则不交换。下面是代码:
static void SelectionSort(int[] arr) { for (int i = 0; i < arr.Length-1; i++) { int min = i; for (int j = i+1; j < arr.Length; j++) { if(arr[min]>arr[j]) { min = j; } } if(min!=i) { int temp = arr[min]; arr[min] = arr[i]; arr[i] = temp; } } }
3.插入:
插入的中心思想是,假设抽出一位元素,向前对比,直到抽出的元素大于循环到的元素为止。要注意的点是:在实际写作时,我们假设第一位是已排好的的序列,抽出第二位,向前遍历比较;其实最内层实现也有两种方法,一种是后移的办法,一种是交换办法,本例中是交换的方式。下面是代码:
static void InsertSort(int[] arr) { for (int i = 1; i < arr.Length; i++) { int j = i; while (j-1>=0&&arr[j]<arr[j-1]) { int temp = arr[j]; arr[j] = arr[j - 1]; arr[j - 1] = temp; j--; } } }
4.希尔:
希尔其实是插入排序的优化版,其中心思想是将数组按照一定的增量,分成若干的子序列,子序列内部排序完成后,增量减少,再次循环。所以可以说插入排序是增量为1的希尔排序。其中我们初始增量设置为arr.length/2,每次循环减少一半。下面是代码:
static void ShellSort(int[] arr) { for (int gap = arr.Length/2; gap > 0; gap/=2) { for (int i = gap; i < arr.Length; i++) { int j = i; while (j-gap>=0&&arr[j]<arr[j-gap]) { int temp = arr[j]; arr[j] = arr[j - gap]; arr[j - gap] = temp; j -= gap; } } } }
5.归并:
归并排序的中心思想是,将数组分从中分开(这是一个递归的过程,直到数组不能再分了),子序列内部排序号,再合并起来。不断重复这个过程,最终数组变得有序。需要注意的是,如果数组太大了,建议还是别使用递归的方式了,因为会StackOverFlow,道理都懂的。代码如下:
static void MergeSort(int[] arr,int left,int right) { if(left<right) { int mid = (left + right) / 2; MergeSort(arr, left, mid); MergeSort(arr, mid + 1, right); Merge(arr, left, mid, right); } } static void Merge(int[] arr,int left,int mid,int right) { int i = left;//作为左边数组的起点 int j = mid + 1;//作为右边数组的起点 int[] temp = new int[right + 1];//加1大家应该知道是什么意思吧? int tIndex = 0;//作为临时数组的索引 while (i<=mid&&j<=right) { if (arr[i]<arr[j]) { temp[tIndex] = arr[i]; i++;tIndex++; } else { temp[tIndex] = arr[j]; j++;tIndex++; } } //接下来还需要再次检测左右数组有无剩下的元素 while(i<=mid) { temp[tIndex] = arr[i]; i++;tIndex++; } while (j<=right) { temp[tIndex] = arr[j]; j++;tIndex++; } //然后将临时数组的元素复制到原来的数组中去 tIndex = 0; for (int k = left; k <= right; k++) { arr[k] = temp[tIndex++]; } }
6.快排:
快排就是给基准数找位置的一个过程。假设左边第一个是基准数,先移动右哨兵的位置,找到一个比基准数小的数停下来,赋值给当前左哨兵;然后再动右哨兵,直到找到一个比基准数大的数停下来,赋值给当前左哨兵。直到两个哨兵相遇,然后将基准数归位。下面是代码:
static void QuickSort(int[] arr,int left,int right) { if (left<right) { int i = left;//左哨兵 int j = right;//右哨兵 int key = arr[left];//基准数 while (i<j) { while (i<j&&arr[j]>=key)//右哨兵所指元素如果比基准数小,则停止 { j--; } if (i<j) { arr[i] = arr[j]; i++; } while (i<j&&arr[i]<=key)//左哨兵所指元素如果比基准数大,则停止 { i++; } if (i<j) { arr[j] = arr[i]; j--; } } arr[i] = key;//基准数归位 QuickSort(arr, left, i); QuickSort(arr, i + 1, right); } }
目前快排掌握的不是很牢靠,时不时会忘掉,没办法只能一遍遍的写,一次次的记了。学校有人教不学,现在自己学有些许的痛苦啊。。。。