和我的LeetCode系列源码放到一起。做IT行业,不管是测试、开发、运维等等,或简单或复杂的算法是必不可少的,也是大家面试工作中的必要环节,这个专栏开始和大家一起来研究著名的LeetCode,里边有上千种最常见的算法,面试工作出现几率很高,值得掌握研究,每次完成博客更新我会同步更新我的个人Github上的代码,每个算法都可以直接运行调试以供掌握,GitHub地址:https://github.com/cuiguangwei/LeetCodeProject.git,欢迎大家一起学习讨论进步!
前言
查找和排序算法是算法的入门知识,其经典思想可以用于很多算法当中。因为其实现代码较短,应用较常见。所以在面试中经常会问到排序算法及其相关的问题。但万变不离其宗,只要熟悉了思想,灵活运用也不是难事。一般在面试中最常考的是快速排序和归并排序,并且经常有面试官要求现场写出这两种排序的代码。对这两种排序的代码一定要信手拈来才行。还有插入排序、冒泡排序、堆排序、基数排序、桶排序等。面试官对于这些排序可能会要求比较各自的优劣、各种算法的思想及其使用场景。还有要会分析算法的时间和空间复杂度。通常查找和排序算法的考察是面试的开始,如果这些问题回答不好,估计面试官都没有继续面试下去的兴趣都没了。所以想开个好头就要把常见的排序算法思想及其特点要熟练掌握,有必要时要熟练写出代码。
接下来我们就分析一下常见的排序算法及其使用场景。
十种常见排序算法可以分为两大类:
(选泡插,快归堆希统计基)
比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
复杂度对比:
冒泡排序
冒泡排序是最简单的排序之一了,其大体思想就是通过与相邻元素的比较和交换来把小的数交换到最前面。这个过程类似于水泡向上升一样,因此而得名。举个栗子,对5、3、8、6、4这个无序序列进行冒泡排序。首先从后向前冒泡,4和6比较,把4交换到前面,序列变成5、3、8、4、6;同理4和8交换,变成5、3、4、8、6;3和4无需交换。5和3交换,变成3、5、4、8、6。这样一次冒泡就完了,把最小的数3排到最前面了。对剩下的序列依次冒泡就会得到一个有序序列。冒泡排序的时间复杂度为O(n^2)。
具体过程:
一趟:
比较相邻的元素。如果第一个比第二个大,就交换它们两个;
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
针对所有的元素重复以上的步骤,除了最后一个;
(一趟下来,最大的那个数就排在了最后面,那么下一趟对前n-1个数做同样的操作)
重复步骤1~3,直到排序完成。
动图演示
黄色表示已排序部分,蓝色表示未排序部分,
实现代码:
Java实现:
/**
* 冒泡排序
*
* @param arr
* @return
*/
public static void BubSort(int[] arr) {
for (int i = 0; i < arr.length; 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;
}
}
}
}
C++实现:
void BubbleSort::bubbleSort(int arr[], int n) {
if (arr == nullptr || n == 0)
return;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n - 1 - i; ++j) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
}
选择排序
选择排序的思想其实和冒泡排序有点类似,都是在一次排序后把最小的元素放到最前面。但是过程不同,冒泡排序是通过相邻的比较和交换。而选择排序是通过对整体的选择。举个栗子,对5,3,8,6,4这个无序序列进行简单选择排序,首先要选择5以外的最小数来和5交换,也就是选择3和5交换,一次排序后就变成了3,5,8,6,4.对剩下的序列一次进行选择和交换,最终就会得到一个有序序列。其实选择排序可以看成冒泡排序的优化,因为其目的相同,只是选择排序只有在确定了最小数的前提下才进行交换,大大减少了交换的次数。选择排序的时间复杂度为O(n^2)。
算法描述:
n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:
初始状态:无序区为R[1..n],有序区为空;
第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
n-1趟结束,数组有序化了。
动图演示:
黄色表示已排序部分,蓝色表示未排序部分,红色表示从未排序中选择的最小值
实现代码:
Java实现:
pubilc static int[] selectionSort(int[] arr) {
int len = arr.length;
int index , temp;
for (int i = 0; i < len - 1; i++) {
index = i;
for (int j = i + 1; j < len; j++) {
if (arr[j] < arr[index ]) { // 寻找最小的数
index = j; // 将最小数的索引保存
}
}
if (index != i) {
int temp = arr[i];
arr[i] = arr[index];
arr[index] = temp;
}
}
return arr;
}
C++实现:
//选择排序法
//从该位置后面选择最小的元素放在该位置
public class SelectSort {
public static void selectSort(int arr[], int n) {
if(arr == nullptr || n == 0)
return ;
int minIndex = 0;
for(int i=0; i < n - 1; i++) { //只需要比较n-1次
minIndex = i;
for(int j = i+1; j < n; j++) { //从i+1开始比较,因为minIndex默认为i了,i就没必要比了。
if(arr[j] < arr[minIndex]) {
minIndex = j;
}
}
if(minIndex != i) { //如果minIndex不为i,说明找到了更小的值,交换之。
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
}
插入排序
插入排序不是通过交换位置而是通过比较找到合适的位置插入元素来达到排序的目的的。相信大家都有过打扑克牌的经历,特别是牌数较大的。在分牌时可能要整理自己的牌,牌多的时候怎么整理呢?就是拿到一张牌,找到一个合适的位置插入。这个原理其实和插入排序是一样的。举个栗子,对5,3,8,6,4这个无序序列进行简单插入排序,首先假设第一个数的位置时正确的,想一下在拿到第一张牌的时候,没必要整理。然后3要插到5前面,把5后移一