梦想成真----数据结构(排序)
Posted 程序员面试之道
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了梦想成真----数据结构(排序)相关的知识,希望对你有一定的参考价值。
梦想成真----数据结构(排序)
1.序
从今天开始我分模块推出面试指南,首先作为程序员最重要的是数据结构,数据结构是我们的本科课程,同时也是我们的必备课程。排序是我们的必考内容,今天以排序算法引出我们的数据结构。
2.总
3.详叙
3.1插入排序
3.1.1直接插入
博客链接(详解)
https://blog.csdn.net/weixin_41563161/article/details/100858281
叙述:
将待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,就得到一个新的序列。(抓扑克牌思想)
特点:元素越接近有序,直接插入排序算法的时间效率越高,算法的本质是减治算法;因此当数组接近有序或大概率有序的情况下,或是数组的数量比较小的时候采用插入排序。
时间复杂度
最好情况:(有序且为正序)O(n)
最坏情况:(逆序)O(n^2)
平均情况:O(n2)
空间复杂度:O(1)
具有稳定性
/**
* @Author liuhaidong
* @Description 直接插入排序
* @Date 16:29 2019/10/2 0002
*/
private static void InsertSort(int[] arr) {
int tmp = 0;
int i ,j;
for(i = 1; i < arr.length; i++) {
/**
* temp为本次循环待插入有序列表中的数
*/
tmp = arr[i];
for(j = i-1;j >=0 && arr[j] > tmp;j--)
{
/**
* 元素后移,为插入temp做准备
*/
arr[j+1] = arr[j];
}
/**
* 插入temp
*/
arr[j+1] = tmp;
}
}
一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
⒈ 从第一个元素开始,该元素可以认为已经被排序
⒉ 取出下一个元素,在已经排序的元素序列中从后向前扫描
⒊ 如果该元素(已排序)大于新元素,将该元素移到下一位置
⒋ 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
⒌ 将新元素插入到下一位置中
⒍ 重复步骤2~5
如果比较操作的代价比交换操作大的话,可以采用二分查找法来减少比较操作的数目。该算法可以认为是插入排序的一个变种,称为二分查找排序。
实例
基本思想:
把n个待排序的元素看成一个有序表和一个无序表,开始时有序表中只有一个元素,无序表中有n-1个元素;排序过程即每次从无序表中取出第一个元素,将它插入到有序表中,使之成为新的有序表,重复n-1次完成整个排序过程。
实例:
0.初始状态 3,1,5,7,2,4,9,6(共8个数)
有序表:3;无序表:1,5,7,2,4,9,6
1.第一次循环,从无序表中取出第一个数 1,把它插入到有序表中,使新的数列依旧有序
有序表:1,3;无序表:5,7,2,4,9,6
2.第二次循环,从无序表中取出第一个数 5,把它插入到有序表中,使新的数列依旧有序
有序表:1,3,5;无序表:7,2,4,9,6
3.第三次循环,从无序表中取出第一个数 7,把它插入到有序表中,使新的数列依旧有序
有序表:1,3,5,7;无序表:2,4,9,6
4.第四次循环,从无序表中取出第一个数 2,把它插入到有序表中,使新的数列依旧有序
有序表:1,2,3,5,7;无序表:4,9,6
5.第五次循环,从无序表中取出第一个数 4,把它插入到有序表中,使新的数列依旧有序
有序表:1,2,3,4,5,7;无序表:9,6
6.第六次循环,从无序表中取出第一个数 9,把它插入到有序表中,使新的数列依旧有序
有序表:1,2,3,4,5,7,9;无序表:6
7.第七次循环,从无序表中取出第一个数 6,把它插入到有序表中,使新的数列依旧有序
有序表:1,2,3,4,5,6,7,9;无序表:(空)
3.1.2希尔排序
博客链接(详解)
https://blog.csdn.net/weixin_41563161/article/details/101637859
叙述:
先将数据进行分组,通过插入排序的方式让数据基本有序,然乎再进行插入排序,能够让插入排序最坏情况的概率降低;
分组的方法:固定步长取值分为一组的方法。分组越多(步长越小)大的数往后走的越快,分组越少排完后越接近有序
希尔排序是对直接插入排序的优化
时间复杂度:
最好情况(正序):O(n)
平均情况:O(n1.3–n2)
最坏情况(逆序):O(n2)
空间复杂度:O(1)
不具有稳定性的排序
/**
* @Author liuhaidong
* @Description 希尔排序
* @Date 16:46 2019/10/2 0002
*/
private static void ShellSort(int[] arr) {
//初始化一个间隔
int h = 1;
//计算最大间隔
while(h < arr.length / 3) {
h = h * 3 + 1;
}
while(h > 0) {
//进行插入排序
int tmp = 0;
for(int i = h; i < arr.length; i++) {
tmp = arr[i];
int j = i;
while(j > h - 1 && arr[j - h] >= tmp) {
arr[j] = arr[j - h];
j -= h;
}
arr[j] = tmp;
}
//减小间隔
h = (h - 1) / 3;
}
}
3.2交换排序
3.2.1冒泡排序
博客链接(详解)
https://blog.csdn.net/weixin_41563161/article/details/100858281
根据序列中两个记录的值的比较结构来交换两个记录在序列中的位置,将值较小的向前移动
特点:算法的本质是减治排序
时间复杂度:
最好情况(正序):O(n)
最坏情况(逆序):O(n2)
平均情况:O(n2)
空间复杂度:O(1)
稳定性:稳定
/**
* @Author liuhaidong
* @Description 冒泡排序
* @Date 14:35 2019/10/2 0002
*/
private static void BubbleSort(int[] source) {
int tmp = 0;
for(int i =0;i<source.length;i++){
for (int j =0;j<source.length-1-i;j++){
if(source[j]>source[j+1]){
tmp = source[j+1];
source[j+1] = source[j];
source[j] = tmp;
}
}
}
// //方法二
// int tmp = 0;
// for(int i=0;i<arr.length-1;i++){
// for(int j=arr.length-1;j>i;j--){
// //进行交换
// if(arr[j] <arr[j-1]){
// tmp = arr[j];
// arr[j] = arr[j -1];
// arr[j-1] = tmp;
// }
// }
// }
}
3.2.2快速排序
博客链接(详解)
https://blog.csdn.net/weixin_41563161/article/details/101921177
一共包含三大步:
1.确定待排序元素序列中的某元素作为基准值(选区间最右边的元素/选中间元素 与最右边的元素交换)
2.以基准值为界,大于基准值的放在基准值的右边,小于基准值的放在基准值左边(hover 挖坑 前后下标)
3.然后对左右序列重复该过程,直到小区间中只有一个元素或没有元素为止(size==0||size ==1)
特点:采用分治算法
时间复杂度:
最好情况:O(n*long(n)) (区间分割,组成形式可以考虑为二叉树形式,最好情况就成了单支树,分治算法变成了减治算法)
平均情况:O(n*long(n))
最差情况:O(n2)
空间复杂度:O(log(n))—O(n)
稳定性:不稳定
/**
* @Author liuhaidong
* @Description快速排序
* @Date 15:51 2019/10/2 0002
*/
private static void QuickSort(int[] arr, int low, int high) {
if(low < high){
// 找寻基准数据的正确索引
int index = GetIndex(arr, low, high);
// 进行迭代对index之前和之后的数组进行相同的操作使整个数组变成有序
QuickSort(arr, 0, index - 1);
QuickSort(arr, index + 1, high);
}
}
private static int GetIndex(int[] arr, int low, int high) {
int i = low, j = high;
int x = arr[low];
//s[l]即s[i]就是第一个坑
while (i < j)
{
// 从右向左找小于x的数来填s[i]
while(i < j && arr[j] >= x){
j--;
}
if(i < j)
{
arr[i] = arr[j];
//将s[j]填到s[i]中,s[j]就形成了一个新的坑
i++;
}
// 从左向右找大于或等于x的数来填s[j]
while(i < j && arr[i] < x)
{
i++;
}
if(i < j)
{
arr[j] = arr[i];
//将s[i]填到s[j]中,s[i]就形成了一个新的坑
j--;
}
}
//退出时,i等于j。将x填到这个坑中。
arr[i] = x;
return i;
}
快速排序我们可以近似的想成一个完全二叉树
一个数组的容量是N,第一层递归遍历的次数是N,因为数组里每个数字都需要和key比较,那么接下来遍历分出来的两个区间,总的遍历次数还会是近似是N,以此类推,直到分为最后一个,也就是说树的高度是lgn,也就是递归次数,所以时间复杂度是N*lgN.
3.3交换排序
3.3.1选择排序
博客链接(详解)
https://blog.csdn.net/weixin_41563161/article/details/100858281
每一次从待排序的元素中选出最小的一个元素,若它不是这组元无序素中的最后一个元素,则将它与存放这组有序元素的起始位置交换,直到全部待排序元素全部排完。
或是从无序元素中选出一个最大的,若其不是无序区间的最后一个元素,则将其与存放有序区间的最后一个元素交换,直到无序元素全部排完
特点:效率不高,算法的本质是减治算法
时间复杂度:
最好情况:O(n2)
平均情况:O(n2)
最坏情况:O(n2)
空间复杂度:O(1)
稳定性:不稳定
/**
* liuhaidong
* 选择排序
* 17:10 2019/10/2 0002
*/
private static void SelectionSort(int[] arr) {
int k =0;
int tmp = 0;
for(int i = 0;i<arr.length-1;i++){
k = i;
for(int j =i+1;j<arr.length;j++){
if(arr[j] <arr[k]){
k = j;
}
}
tmp = arr[i];
arr[i] = arr[k];
arr[k] = tmp;
}
}
3.3.1堆排序
博客链接(详解)
https://blog.csdn.net/weixin_41563161/article/details/101937543
利用堆的特点设计的一种排序算法,排升序要建大堆,排降序要建小堆,建堆是从第一个非叶子节点开始建,利用向下调整的思想逐步建立出整个堆。
每次将未排序序列的第一个与最后一个元素进行交换,然后利用向下调整,将最大的元素调整到最后一个。
特点:算法的本质是减治算法
时间复杂度:
最好情况:O(n*longn)
平均情况:O(n*longn)
最坏情况:O(n*longn)
空间复杂度:O(1)
稳定性:不稳定
/**
* @Author liuhaidong
* @Description 堆排序
* @param
* @Date 20:30 2019/10/2 0002
*/
private static void HeadSort(int[] arr) {
int startIndex = (arr.length-1)/2;
//定义开始调整的位置
for(int i =startIndex;i>=0;i--){
ToMaxHeap(arr,arr.length,i);
//调整成大顶堆的方法
}
//经过上面的操作后,已经把数组变成一个大顶堆,把根元素和最后一个元素进行调换
for(int i = arr.length-1;i>0;i--){
int t = arr[0];
arr[0] = arr[i];
arr[i] = t;
//进行调换
ToMaxHeap(arr,i,0);
//换完之后我们再把剩余元素换成大顶堆
}
}
/**
* @Author liuhaidong
* @Description
* @param //arr要排序的数组 size调整元素的个数 index从哪里开始调整
* @Date 19:38 2019/10/2 0002
*/
private static void ToMaxHeap(int[] arr, int size, int index) {
int leftNodeIndex = index * 2 + 1;
int rightNodeIndex = index * 2 + 2;
//获取左右字节的索引
int maxIndx = index;
if (leftNodeIndex < size && arr[leftNodeIndex] > arr[maxIndx]) {
maxIndx = leftNodeIndex;
}
if (rightNodeIndex < size && arr[rightNodeIndex] > arr[maxIndx]) {
maxIndx = rightNodeIndex;
}
//查找最大节点所对应的索引
if (maxIndx != index) {
int t = arr[maxIndx];
arr[maxIndx] = arr[index];
arr[index] = t;
//调换完成后,可能会影响到下面的子树,不是大顶堆,我们还需要再次调整
ToMaxHeap(arr, size, index);
}
}
3.4归并排序
博客链接(详解)
https://blog.csdn.net/weixin_41563161/article/details/101942087
把数组平均分成两部分,分别对左右两部分做均分,直到区间的size<=1,然后利用一个额外的数组空间,逐步将两个数组按序排列,再将排好的数据拷贝回原始数组,这样使子序列的段间有序,将子序列归并为完整的序列
特点:分治算法,是外部排序最好的算法
时间复杂度:
最好情况:O(n*log(n))
空间复杂度:O(n)
稳定性:稳定
需要做外部排序的情况:
1.内存放不下了,切成小块,将每一小块排序,然后合并n个有序数组
/**
* @Author liuhaidong
* @Description 归并排序
* @param
* @Date 21:48 2019/10/2 0002
*/
private static void MergrSort(int[] arr) {
Split(arr,0,arr.length-1);
//拆分 我们先给一个左右两边是有序的一个数组,先来进行归并排序 {4,5,7,8, 1,2,3,6}
}
//进行拆分
private static void Split(int[] arr, int startIndex, int endIndex) {
//计算中间索引
int centerIndex = (startIndex + endIndex)/2;
if(startIndex < endIndex){
Split(arr, startIndex, centerIndex);
Split(arr, centerIndex+1, endIndex);
Mergr(arr,startIndex,centerIndex,endIndex);
//进行归并
}
}
//进行归并
private static void Mergr(int[] arr, int startIndex, int centerIndex, int enIndex) {
int[] tempArr = new int[enIndex-startIndex+1];
//定义一个临时数组
int i = startIndex;
//定义一个左边数组的起始索引
int j = centerIndex+1;
//定义一个右边数组的起始索引
int index = 0;
//定义一个临时数组的起始索引
while (i <= centerIndex && j<=enIndex){
//比较左右两个数组的元素大小,往临时数组中放
if(arr[i] <= arr[j]){
tempArr[index] = arr[i];
i++;
}else {
tempArr[index] = arr[j];
j++;
}
index++;
}
//处理剩余元素 可能是左边元素,也可能是右边元素
while(i <= centerIndex){
tempArr[index] = arr[i];
i++;
index++;
}
while(j <= enIndex){
tempArr[index] = arr[j];
j++;
index++;
}
for(int k = 0;k<tempArr.length;k++){
arr[k+startIndex] = tempArr[k];
}
}
4.总结
其中快速排序、堆排序 、归并排序必须要能默写出来的。因为篇幅问题所以不能展开,之后会对每一个算法进行展开讲解。一定要了解每个排序算法的原理,做到胸有成竹,这样才可以梦想成真。
https://blog.csdn.net/weixin_41563161
微信
以上是关于梦想成真----数据结构(排序)的主要内容,如果未能解决你的问题,请参考以下文章
初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段
ElasticSearch学习问题记录——Invalid shift value in prefixCoded bytes (is encoded value really an INT?)(代码片段