做者: C you again,从事软件开发 努力在IT搬砖路上的技术小白java
公众号: 【C you again】,分享计算机类毕业设计源码、IT技术文章、游戏源码、网页模板、程序人生等等。公众号回复 【粉丝】进博主技术群,与大佬交流,领取干货学习资料
关于转载:欢迎转载博主文章,转载时代表出处
求赞环节:创做不易,记得 点赞+评论+转发 谢谢你一路支持git
在前面三期,介绍了动态规划的两个主要特性:交叠子问题和最优子结构,并用七种方式求解第n项斐波那契数,感觉了算法的强大,你是否也领略到它的精髓呢?面试
今天咱们就来讨论面试官最喜欢问到的排序算法吧,从冒泡排序、选择排序、插入排序等十大排序算法的排序步骤、代码实现两个方面入手,完全搞清实现原理,保证面试道路一路畅通。服务器
所谓排序算法,就是经过特定的算法因式将一组或多组数据按照必定模式进行从新排序。网络
这种新序列遵循着必定的规则,体现出必定的规律,所以,经处理后的数据便于筛选和计算,大大提升了计算效率。数据结构
(1)时间复杂度:即从序列的初始状态到通过排序算法的变换移位等操做变到最终排序好的结果状态的过程所花费的时间度量。架构
(2)空间复杂度:就是从序列的初始状态通过排序移位变换的过程一直到最终的状态所花费的空间开销。
(3)稳定性:稳定性是无论考虑时间和空间必需要考虑的问题,每每也是很是重要的影响选择的因素。
冒泡排序是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,若是他们的顺序错误就把他们交换过来。走访数列的工做是重复地进行直到没有再须要交换的数据,也就是说该数列已经排序完成。这个算法的名字由来是由于越小的元素会经由交换慢慢"浮"到数列的顶端。
(1)算法步骤
步骤1:比较相邻的元素。若是第一个比第二个大,就交换他们两个;
步骤2:对每一对相邻元素做一样的工做,从开始第一对到结尾的最后一对。这步作完后,最后的元素会是最大的数;
步骤3:针对全部的元素重复以上的步骤,除了最后一个;
步骤4:重复步骤1~3,直到排序完成;
(2)代码实现
public class BubbleSort implements IArraySort { @Override public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); for (int i = 1; i < arr.length; i++) { // 设定一个标记,若为true,则表示这次循环没有进行交换,也就是待排序列已经有序,排序已经完成。 boolean flag = true; for (int j = 0; j < arr.length - i; j++) { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; flag = false; } } if (flag) { break; } } return arr; } }
选择排序是一种简单直观的排序算法,不管什么数据进去都是 O(n²) 的时间复杂度。因此用到它的时候,数据规模越小越好。
(1)算法步骤
步骤1:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置;
步骤2:再从剩余未排序元素中继续寻找最小(大)元素,而后放到已排序序列的末尾;
步骤3:重复步骤2,直到全部元素均排序完毕;
(2)代码实现
public class SelectionSort implements IArraySort { @Override public int[] sort(int[] sourceArray) throws Exception { int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); // 总共要通过 N-1 轮比较 for (int i = 0; i < arr.length - 1; i++) { int min = i; // 每轮须要比较的次数 N-i for (int j = i + 1; j < arr.length; j++) { if (arr[j] < arr[min]) { // 记录目前能找到的最小值元素的下标 min = j; } } // 将找到的最小值和i位置所在的值进行交换 if (i != min) { int tmp = arr[i]; arr[i] = arr[min]; arr[min] = tmp; } } return arr; } }
插入排序的算法描述是一种简单直观的排序算法。它的工做原理是经过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,一般采用in-place排序(即只需用到O(1)的额外空间的排序),于是在从后向前扫描过程当中,须要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
(1)算法步骤
步骤1:从第一个元素开始,该元素能够认为已经被排序;
步骤2:取出下一个元素,在已经排序的元素序列中从后向前扫描;
步骤3:若是该元素(已排序)大于新元素,将该元素移到下一位置;
步骤4:重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
步骤5:将新元素插入到该位置后;
步骤6:重复步骤2~5;
(2)代码实现
public class InsertSort implements IArraySort { @Override public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); // 从下标为1的元素开始选择合适的位置插入,由于下标为0的只有一个元素,默认是有序的 for (int i = 1; i < arr.length; i++) { // 记录要插入的数据 int tmp = arr[i]; // 从已经排序的序列最右边的开始比较,找到比其小的数 int j = i; while (j > 0 && tmp < arr[j - 1]) { arr[j] = arr[j - 1]; j--; } // 存在比其小的数,插入 if (j != i) { arr[j] = tmp; } } return arr; } }
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序是基于插入排序的如下两点性质而提出改进方法的:
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
(1)算法步骤
步骤1:选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
步骤2:按增量序列个数 k,对序列进行 k趟排序;
步骤3:每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1时,整个序列做为一个表来处理,表长度即为整个序列的长度;
(2)代码实现
public class ShellSort implements IArraySort { @Override public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); int gap = 1; while (gap < arr.length) { gap = gap * 3 + 1; } while (gap > 0) { for (int i = gap; i < arr.length; i++) { int tmp = arr[i]; int j = i - gap; while (j >= 0 && arr[j] > tmp) { arr[j + gap] = arr[j]; j -= gap; } arr[j + gap] = tmp; } gap = (int) Math.floor(gap / 3); } return arr; } }
归并排序是创建在归并操做上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个很是典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,获得彻底有序的序列;即先使每一个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
和选择排序同样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,由于始终都是O(n log n)的时间复杂度。代价是须要额外的内存空间。
(1)算法步骤
步骤1:把长度为n的输入序列分红两个长度为n/2的子序列;
步骤2:对这两个子序列分别采用归并排序;
步骤3:将两个排序好的子序列合并成一个最终的排序序列;
(2)代码实现
public class MergeSort implements IArraySort { @Override public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); if (arr.length < 2) { return arr; } int middle = (int) Math.floor(arr.length / 2); int[] left = Arrays.copyOfRange(arr, 0, middle); int[] right = Arrays.copyOfRange(arr, middle, arr.length); return merge(sort(left), sort(right)); } protected int[] merge(int[] left, int[] right) { int[] result = new int[left.length + right.length]; int i = 0; while (left.length > 0 && right.length > 0) { if (left[0] <= right[0]) { result[i++] = left[0]; left = Arrays.copyOfRange(left, 1, left.length); } else { result[i++] = right[0]; right = Arrays.copyOfRange(right, 1, right.length); } } while (left.length > 0) { result[i++] = left[0]; left = Arrays.copyOfRange(left, 1, left.length); } while (right.length > 0) { result[i++] = right[0]; right = Arrays.copyOfRange(right, 1, right.length); } return result; } }
快速排序是由东尼·霍尔所发展的一种排序算法。在平均情况下,排序n个项目要 Ο(n log n) 次比较。在最坏情况下则须要 Ο(n^2) 次比较,但这种情况并不常见。事实上,快速排序一般明显比其余 Ο(nlogn) 算法更快,由于它的内部循环(inner loop)能够在大部分的架构上颇有效率地被实现出来。
快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。
(1)算法步骤
步骤1:从数列中挑出一个元素,称为 "基准"(pivot);
步骤2:从新排序数列,全部元素比基准值小的摆放在基准前面,全部元素比基准值大的摆在基准的后面(相同的数能够到任一边)。在这个分区退出以后,该基准就处于数列的中间位置。这个称为分区(partition)操做;
步骤3:递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
(2)代码实现
public class QuickSort implements IArraySort { @Override public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); return quickSort(arr, 0, arr.length - 1); } private int[] quickSort(int[] arr, int left, int right) { if (left < right) { int partitionIndex = partition(arr, left, right); quickSort(arr, left, partitionIndex - 1); quickSort(arr, partitionIndex + 1, right); } return arr; } private int partition(int[] arr, int left, int right) { // 设定基准值(pivot) int pivot = left; int index = pivot + 1; for (int i = index; i <= right; i++) { if (arr[i] < arr[pivot]) { swap(arr, i, index); index++; } } swap(arr, pivot, index - 1); return index - 1; } private void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
堆排序是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似彻底二叉树的结构,并同时知足堆积的性质:即子结点的键值或索引老是小于(或者大于)它的父节点。堆排序能够说是一种利用堆的概念来排序的选择排序。分为两种方法:
堆排序的平均时间复杂度为 O(n log n)。
(1)算法步骤
步骤1:将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
步骤2:将堆顶元素R[1]与最后一个元素R[n]交换,此时获得新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且知足R[1,2…n-1]<=R[n];
步骤3:因为交换后新的堆顶R[1]可能违反堆的性质,所以须要对当前无序区(R1,R2,……Rn-1)调整为新堆,而后再次将R[1]与无序区最后一个元素交换,获得新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成;
(2)代码实现
public class HeapSort implements IArraySort { @Override public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); int len = arr.length; buildMaxHeap(arr, len); for (int i = len - 1; i > 0; i--) { swap(arr, 0, i); len--; heapify(arr, 0, len); } return arr; } private void buildMaxHeap(int[] arr, int len) { for (int i = (int) Math.floor(len / 2); i >= 0; i--) { heapify(arr, i, len); } } private void heapify(int[] arr, int i, int len) { int left = 2 * i + 1; int right = 2 * i + 2; int largest = i; if (left < len && arr[left] > arr[largest]) { largest = left; } if (right < len && arr[right] > arr[largest]) { largest = right; } if (largest != i) { swap(arr, i, largest); heapify(arr, largest, len); } } private void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
计数排序 的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。做为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有肯定范围的整数。
计数排序是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。而后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。
(1)算法步骤
步骤1:找出待排序的数组中最大和最小的元素;
步骤2:统计数组中每一个值为i的元素出现的次数,存入数组C的第i项;
步骤3:对全部的计数累加(从C中的第一个元素开始,每一项和前一项相加);
步骤4:反向填充目标数组:将每一个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1;
(2)代码实现
public class CountingSort implements IArraySort { @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; } }
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的肯定。为了使桶排序更加高效,咱们须要作到这两点:
同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响相当重要。
(1)算法步骤
步骤1:人为设置一个BucketSize,做为每一个桶所能放置多少个不一样数值(例如当BucketSize==5时,该桶能够存放{1,2,3,4,5}这几种数字,可是容量不限,便可以存放100个3);
步骤2:遍历输入数据,而且把数据一个一个放到对应的桶里去;
步骤3:对每一个不是空的桶进行排序,可使用其它排序方法,也能够递归使用桶排序;
步骤4:从不是空的桶里把排好序的数据拼接起来;
注意,若是递归使用桶排序为各个桶排序,则当桶数量为1时要手动减少BucketSize增长下一循环桶的数量,不然会陷入死循环,致使内存溢出;
(2)代码实现
/** * 桶排序 * * @param array * @param bucketSize * @return */ public static ArrayList<Integer> BucketSort(ArrayList<Integer> array, int bucketSize) { if (array == null || array.size() < 2) return array; int max = array.get(0), min = array.get(0); // 找到最大值最小值 for (int i = 0; i < array.size(); i++) { if (array.get(i) > max) max = array.get(i); if (array.get(i) < min) min = array.get(i); } int bucketCount = (max - min) / bucketSize + 1; ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketCount); ArrayList<Integer> resultArr = new ArrayList<>(); for (int i = 0; i < bucketCount; i++) { bucketArr.add(new ArrayList<Integer>()); } for (int i = 0; i < array.size(); i++) { bucketArr.get((array.get(i) - min) / bucketSize).add(array.get(i)); } for (int i = 0; i < bucketCount; i++) { if (bucketSize == 1) { // 若是待排序数组中有重复数字时 for (int j = 0; j < bucketArr.get(i).size(); j++) resultArr.add(bucketArr.get(i).get(j)); } else { if (bucketCount == 1) bucketSize--; ArrayList<Integer> temp = BucketSort(bucketArr.get(i), bucketSize); for (int j = 0; j < temp.size(); j++) resultArr.add(temp.get(j)); } } return resultArr; }
基数排序也是非比较的排序算法,对每一位进行排序,从最低位开始排序,复杂度为O(kn),为数组长度,k为数组中的数的最大的位数;
基数排序是按照低位先排序,而后收集;再按照高位排序,而后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,因此是稳定的。
(1)算法步骤
步骤1:取得数组中的最大数,并取得位数;
步骤2:arr为原始数组,从最低位开始取每一个位组成radix数组;
步骤3:对radix进行计数排序(利用计数排序适用于小范围数的特色);
(2)代码实现
/** * 基数排序 */ public class RadixSort implements IArraySort { @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; } }
推荐一:计算机网络中这些高频考题,你还在死记硬背吗?(一),讲述内容:IP地址及其分类,子网掩码的概念,网络号、主机号、直接广播地址计算方法等。
推荐二:计算机网络中这些高频考题,你还在死记硬背吗?(二),讲述内容:局域网接口配置、路由器的静态路由配置、OSPF动态路由协议配置和DHCP服务器配置。
推荐三:用x种方式求第n项斐波那契数,99%的人只会第一种,讲述内容:七种方式求解第N项斐波那契数。
以上就是本期的全部内容了,是否对你有帮助呢?了解更多算法请关注公众号“C you again”。