《剑指》里面说,面试官着重考察二分查找、快速排序和归并排序。故来总结下排序问题啦!
目录
排序问题总结
以下重点介绍下归并、快排、堆排序,注意时间复杂度和空间复杂度的比较(都需要一定的辅助空间)
1.快速排序
(1)快排思想
通过一趟排序将数据分割成两部分——在数组中选择一个数字(通常选择第一或者最后一个),比选择数字小的移到数组左边,比选择数字大的移到右边,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程递归进行。
(2)快排java代码
public void quickSort(int[] array,int start,int end){
if(start>end){
return; //此为递归出口,容易忘记
}
int i=start;
int j=end;
int key=array[start]; //选头部元素作为比较对象
//比选择数字小的移到数组左边,比选择数字大的移到右边
while(i<j){
//注意扫描顺序,一定要先扫后半段
while(array[j]>key && i<j) {j--;} //从后半段开始扫描
while(array[i]<=key && i<j) {i++;}//从前半段开始扫描,且是小于等于
if(i<j){
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
//将选择元素换到中间位置来
int temp = array[i];
array[i] = array[start];
array[start] = temp;
//递归
quickSort(array,start,i-1);
quickSort(array,i+1,end);
}
2.归并排序
(1)归并排序思想
采用分治法,将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
(2)归并排序步骤
第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
第四步:重复步骤3直到某一指针超出序列尾,将另一序列剩下的所有元素直接复制到合并序列尾
(3)归并排序java代码
//递归地进行划分操作
public void mergeSort(int[] array,int start,int end){
int mid = (start+end)/2;
if(start<end){ //递归退出条件,如果不判断则会栈溢出
mergeSort(array, start, mid);//左
mergeSort(array, mid+1, end);//右
merge(array,start,mid,end); //左右合并
}
}
//合并有序子序列,实现升序排序(链表合并也是同理)
public void merge(int[] array,int start,int mid,int end){
//新申请一块合并数组空间
int[] new_array = new int[end-start+1];
//建立两个指针,分别指向左右序列的头部
int i=start;
int j=mid+1;
int k=0; //新的合并数组的索引
while(i<=mid && j<=end){
if(array[i]<array[j]){
new_array[k++] = array[i++];
}
else{
new_array[k++] = array[j++];
}
}
//若某个子序列仍有剩余元素,全部加入到合并数组的尾部
while(i<=mid) {new_array[k++] = array[i++];}
while(j<=end) {new_array[k++] = array[j++];}
//将新排好序的数组放入元素相应的位置中,相当于数组的copy操作
for(int index=0;index<new_array.length;index++){
array[start+index] = new_array[index];
}
}
3.堆排序
(1)堆排序思想
堆分为最大堆和最小堆(完全二叉树),最大堆要求节点的元素都要不小于其孩子,最小堆要求节点元素都不大于其左右孩子,两者对左右孩子的大小关系不做任何要求。
我们可以得知,处于最大堆的根节点的元素一定是这个堆中的最大值。堆排序算法就是抓住了堆的这一特点,每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。
(2)堆排序步骤
1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区
2)将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn)
3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成
(3)堆排序java代码
//堆排序,实现升序排序
public int[] heapSort(int[] array){
array = buildMaxHeap(array); //初始建堆,array[0]为第一趟值最大的元素
for(int i=array.length-1;i>1;i--){
int temp = array[0]; //将堆顶元素array[0]和堆底元素array[i]交换,即得到当前最大元素正确的排序位置
array[0] = array[i];
array[i] = temp;
adjustDownToUp(array,0,i); //整理,将剩余的元素整理成堆
}
return array;
}
//构建大根堆:将array看成完全二叉树的顺序存储结构
private int[] buildMaxHeap(int[] array){
//从最后一个节点array.length-1的父节点(array.length-1-1)/2开始,直到根节点0,反复调整堆
for(int i=(array.length-2)/2;i>=0;i--){
adjustDownToUp(array,i,array.length);
}
//输出结果
System.out.println("initial heap");
for(int i=0; i<array.length; i++){
System.out.print(array[i]+" ");
}
return array;
}
//看父节点array[k]是否比子女小,如果是,则与大的交换,array[k]下移,大的子节点上移
//下移的array[k]只要没有到叶节点,就不断比较,判断是否需要下移(k = i的含义)
private void adjustDownToUp(int[] array,int k,int length){
System.out.println("adjust "+array[k]);
int temp = array[k];
for(int i=2*k+1; i<length; i=2*i+1){ //i初始化为节点k的左孩子,沿节点较大的子节点向下调整
if(i<length-1 && array[i]<array[i+1]){ //i<length-1时才存在有孩子,此时比较左右孩子大小
i++; //如果节点的右孩子>左孩子,则取右孩子节点的下标
}
if(temp>=array[i]){ //根节点 >=左右子女中关键字较大者,调整结束
break;
}else{ //根节点 <左右子女中关键字较大者
array[k] = array[i]; //将左右子结点中较大值array[i]调整到双亲节点上
k = i; //【关键】修改k值,以便继续向下调整
}
}
array[k] = temp; //被调整的结点的值放入最终位置
//输出结果
for(int i=0; i<array.length; i++){
System.out.print(array[i]+" ");
}
}