了解排序
排序: 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或者递减排列起来的操作. 稳定性: 在一组元素中可能存在相等的元素,如果经过排序,这些记录的相对次序保持不变,那么认为这种排序是稳定的,否则是不稳定的. 在排序的过程中如果没有间隔交换或者插入,则该种排序算法都是稳定的. 如下图所示.
排序分类
1.插入排序
基本思想:
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列.
现实中的例子:
我们在玩扑克牌的时候,手里的牌是排好序的,当我们摸上来一张牌的时候,就需要把这张牌插入到相应的位置,这就是插入排序.
1.1 直接插入排序
算法思想:
当插入第i个元素时,前面的元素已经排好序了,然后用这个元素和前面的元素依次进行比较,如果找到插入位置,将元素插入,然后将此位置后面的元素依次往后移动.
算法动图演示:
后面的元素依次与前面的元素进行比较,如果小于前面元素,则插入到前面,否则不动.
来举一个简单的小例子. 如图,我们现在要往下面的序列中插入元素3
算法实现步骤: a.找待插入元素的位置 用end指针标记序列最后一个元素,用key和end位置元素比较,如果 end>=0 && key<array[end] 时,将end位置元素后移一位,然后end- -,直到找到相应的位置.
注意:这里的end>=0原因是,如果我们要往当前的例子中插入元素0的话,在最后一步中end- -后就成了负数,会报数组下标越界异常,所以要加一个判断条件.
代码实现:
while(end>=0 && key<array[end]){
array[end+1]=array[end];
end--;
}
图形演示:
end- -以后,key的值(3)大于此时end位置的值(2),所以已经找到了该插入的位置.
b.将元素插入到end+1的位置 从步骤a中可以知道,在找到位置后,end的位置在该插入位置之前的一个单位,所以将key插入到end+1的位置. 代码实现:
array[end+1]=key;
图形演示:
在这里,这个例子就已经讲完了. 但是在实际排序中,整个序列都不一定有序,所以我们一般默认第一个位置元素已经排好序了,然后将这个元素后面的每个元素依次插入到相应的位置. 代码实现如下:
public class InsertSort {
static void printArray(int[] array){
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]+" ");
}
System.out.println();
}
public static void main(String[] args) {
int[] array={7,6,5,8,1,3,9,2,4,0};
for (int i = 1; i < array.length; i++) {
int end=i-1;
int key=array[i];
while (end>=0 && key<array[end]){
array[end+1]=array[end];
end--;
}
array[end+1]=key;
}
printArray(array);
}
}
直接排序算法特性总结:
1.元素集合越接近有序,直接插入排序算法的时间效率越高. 2.时间复杂度(取最坏的情况): O(N^2),最佳的情况为O(N). 3.空间复杂度:O(1),它是一种稳定的排序算法. 4.稳定性:稳定.(大家可以自己想一个例子进行证明) 5.应用场景数据接近有序或者数据量比较小的情况.
1.2 希尔排序(也可以说是插入排序PLUS)
基本思想:
利用分组的方式将数据量降下来,然后对每一组的数据进行插入排序.
算法动图演示:
举例进行解释说明: 先给定一个数据序列:{7,6,5,8,1,3,9,2,4,0}.然后对这个序列进行希尔排序. 1.当gap=10的时候,按照间隔为10分组,就相当没有分组,还是原来的序列.
2.当gap=gap/2=5的时候.按照间隔为5进行分组,然后对每个组进行插入排序
数字颜色相同的在一个组
3.当gap=gap/2=2的时候.按照间隔为2进行分组,然后对每个组进行插入排序
数字颜色相同的在一个组
4.当gap=gap/2=1的时候.按照间隔为1进行分组,也就是对这个数组直接进行插入排序
我们用一个上面的第3步来对代码进行一个简单的解释.(gap=2) a.刚开始i等于gap,标记下标为gap位置的元素. b.用end标记当前分组的前一个元素,也就是i-gap位置的元素int end=i-gap; key记录此时要插入的元素int key=array[i];
c.如果end>=0 && key<array[end] ,将end位置元素后移gap位,然后end-=gap 直到找到合适的位置.其实和插入排序一样,只不过这里的单位间隔是gap不是插入排序默认的1.执行完后i++; 进入下一个组中进行插入排序.让这个步骤循环起来. 代码展示:
for (int i = gap; i < size; i++) {
int end=i-gap;
int key=array[i];
while (end>=0 && key<array[end]){
array[end+gap]=array[end];
end-=gap;
}
array[end+gap]=key;
}
图形理解:
代码实现如下:
public class ShellSort {
static void printArray(int[] array){
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]+" ");
}
System.out.println();
}
public static void main(String[] args) {
int[] array={7,6,5,8,1,3,9,2,4,0};
int size=array.length;
int gap=size;
while (gap>0){
for (int i = gap; i < size; i++) {
int end=i-gap;
int key=array[i];
while (end>=0 && key<array[end]){
array[end+gap]=array[end];
end-=gap;
}
array[end+gap]=key;
}
gap/=2;
}
printArray(array);
}
}
希尔排序的特性总结:
1.希尔排序是对直接插入排序的优化 2.当gap>1时,都是预排序,目的是让数组更加的接近有序.当gap=1的时候,数组已经非常接近有序的了,这样排序就会很快.整体而言,可以达到优化的效果. 3.希尔排序的时间复杂度不好算,因为gap的取值方法有很多,导致很难去计算,希尔排序的时间复杂度不是固定的.因为我们这里是按照Knuth提出的方式进行取值的,而且Knuth进行了大量的实验进行证明统计.所以我们暂时就按照O(n ^ 1.25)到O(1.6 * N^1.25)来算. 4.空间复杂度O(1) 5.稳定性:不稳定.
2.选择排序
基本思想:
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完.
2.1 直接选择排序
基本思想:
在序列中找最小元素(最大元素)的位置,然后将该元素与区间第一个元素(最后一个元素)进行交换.
算法动图演示:
给定一个序列:{7,6,5,8,1,3,9,2,4,0},进行选择排序.这里我们进行升序排列,所以是找最大的元素进行排列.
算法实现步骤: 找到最大元素的位置,然后将该元素与区间最后一个元素进行交换. 图形理解:
第一趟查找第二趟查找: 此时9已经排好序了,所以和9前面的元素进行交换.就这样一直查找并交换,最后会排列成升序序列.
代码实现如下:
public class SelectSort {
static void printArray(int[] array){
for (int i = 0; i <array.length ; i++) {
System.out.print(array[i]+" ");
}
}
static void swap(int[] array,int left,int right){
int temp=array[left];
array[left]=array[right];
array[right]=temp;
}
public static void main(String[] args) {
int[] array={7,6,5,8,1,3,9,2,4,0};
for(int i=0;i<array.length;i++){
int pos=0;
for (int j = 0; j < array.length-i; j++) {
if(array[j]>array[pos]){
pos=j;
}
}
if (pos != array.length-1-i){
swap(array,pos,array.length-i-1);
}
}
printArray(array);
}
}
特别篇: 直接选择排序的优化
基本思想:
用minPos标记最小元素位置,maxPos标记最大元素位置.begin标记区间最左侧元素,end标记区间最右侧元素. 用minPos位置元素与begin位置元素进行交换,maxPos位置元素与end位置元素进行交换.
算法实现步骤: a.刚开始让minPos和maxPos都指向最左侧位置. b.用一个索引index去找最大和最小的元素.index从begin+1 开始
c.当index位置的元素大于begin位置的元素时,用maxPox标记当前位置. 当index位置的元素小于begin位置的元素时,用minPox标记当前位置. 直到找到最大的元素和最小的元素. d.用minPos位置的元素和begin位置的元素进行交换, 用maxPos位置的元素和end位置的元素进行交换. 然后begin++;end--;
第一趟查找交换第二趟查找交换 后面的步骤就依次类推就行.
代码实现如下:
public class SelectSortPlus {
static void printArray(int[] array){
for (int i = 0; i <array.length ; i++) {
System.out.print(array[i]+" ");
}
}
static void swap(int[] array,int left,int right){
int temp=array[left];
array[left]=array[right];
array[right]=temp;
}
public static void main(String[] args) {
int[] array={7,6,5,8,1,3,9,2,4,0};
int begin=0;
int end=array.length-1;
while (begin < end){
int minPos=begin;
int maxPos=begin;
int index=begin+1;
while (index <= end){
if (array[index]>array[maxPos]){
maxPos=index;
}
if (array[index]<array[minPos]){
minPos=index;
}
index++;
}
if (maxPos!=end){
swap(array,maxPos,end);
}
if (minPos==end){
minPos=maxPos;
}
if (minPos!=begin){
swap(array,minPos,begin);
}
begin++;
end--;
}
printArray(array);
}
}
直接选择排序的特性总结:
1.好理解,但是效率不是很好,实际中很少使用. 2.时间复杂度:O(N^2) 3.空间复杂度:O(1) 4.稳定性:不稳定.
2.2 堆排序
想要了解清楚这一部分内容,就要对堆有一个理解. 不熟悉的兄弟们可以点击传送门去了解了解. 传送门:优先级队列(堆) 大家直接去看堆的创建部分就行.有完整的图形解释. 基本思想:
堆排序(Heapsort)是指利用(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。 需要注意的是排升序要建大堆,排降序建小堆。
算法实现步骤:
1.建堆(需要用到向下调整)------>升序建大堆,降序建小堆. a.找倒数第一个非叶子节点:(size-1-1)/2 b.从倒数第一个非叶子的位置倒着往根的方向走:遇到每个节点,将该节点向下调整 2.利用堆删除的思想来进行排序 a.用堆顶的元素和堆中最后一个元素进行交换.就是将最大的元素放在了最后的位置.所以要建大堆 b.将堆中有效元素个数减少一个,最大的值就不用动了. c.将堆顶元素向下调整
代码实现如下:
public class HeapSort {
static void printArray(int[] array){
for (int i = 0; i <array.length ; i++) {
System.out.print(array[i]+" ");
}
}
static void swap(int[] array,int left,int right){
int temp=array[left];
array[left]=array[right];
array[right]=temp;
}
static void shiftDown(int[] array,int size,int parent){
int child=parent*2+1;
while (child<size){
if (child+1<size && array[child+1]>array[child]){
child+=1;
}
if (array[parent]<array[child]){
swap(array,parent,child);
parent=child;
child=parent*2+1;
}else{
return;
}
}
}
public static void main(String[] args) {
int[] array={7,6,5,8,1,3,9,2,4,0};
int size=array.length;
int lastLeaf=(size-2)/2;
for (int root=lastLeaf; root >=0; root--) {
shiftDown(array,size,root);
}
int end=size-1;
while (end>0){
swap(array,0,end);
shiftDown(array,end,0);
end--;
}
printArray(array);
}
}
堆排序的特性总结:
1.堆排序使用堆来选数,效率就高了很多。 2.时间复杂度:O(N*logN) 3.空间复杂度:O(1) 4.稳定性:不稳定
3.交换排序
基本思想:
根据序列中两个元素值的比较结果来对换这两个记录在序列中的位置.
交换排序的特点是:
将值大的元素往序列的尾部移动,序列小的元素往序列的前部移动.
3.1 冒泡排序
基本思想:
通过比较相邻的两个元素,根据结果将两个元素的位置互换. 如果想要升序的话就小元素前移,大元素后移.降序则相反. 假如相邻的两个元素是6,4,那么通过比较,6>4 ,所以两个元素要互换位置,变成4,6.
算法动图演示:
我们还是直接上例子: 给定一个序列:{7,6,5,8,1,3,9,2,4,0},进行冒泡排序.这里我们进行升序排列,所以小元素前移,大元素后移. 算法实现步骤: 两层循环,外层循环控制趟数,内层循环进行遍历比较 a.初始令j=1,标记下标为1的元素,与前一个元素(j-1) 位置元素进行比较. b.如果当前元素小于前一个元素就交换位置,大于前一个元素的话j++ ,再次进行比较.
if (arr[j-1]>arr[j]){
int temp=arr[j-1];
arr[j-1]=arr[j];
arr[j]=temp;
}
用图形理解:
第一趟比较交换: 后面的比较和上面的步骤一样,j又从1开始和j-1位置的元素进行比较交换,只不过这次比较的次数少了一次,因为第一趟中的9已经排好了,就不需要再排一次了
代码实现如下:
public class BubbleSort {
public static void bubbleSort(int[] arr){
for (int i = 0; i <arr.length; i++) {
for (int j = 1; j < arr.length-i; j++) {
if (arr[j-1]>arr[j]){
int temp=arr[j-1];
arr[j-1]=arr[j];
arr[j]=temp;
}
}
}
}
public static void printArray(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
System.out.println();
}
public static void main(String[] args) {
int[] arr={6,4,2,5,8,7,1,3,9,0};
bubbleSort(arr);
printArray(arr);
}
}
冒泡排序的特性总结:
1.是一种非常好理解的排序算法 2.时间复杂度:O(N^2) 3.空间复杂度:O(1) 4.稳定性:稳定
3.2 快速排序(快排)
基本思想:
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两个子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止. 我们都是假设升序进行代码的实现.
算法步骤:
1.找一个基准值:基准值没有规定一定要去怎么找,区间中任何一个数字都可以作为基准值,但是从代码的可行性上说,一般取的是区间两侧的数据. 2.然后按照基准值将区间分割成两个部分:左侧部分比基准值小,右侧部分比基准值大. 3.递归进左侧再次进行分割,递归进右侧再次进行分割. 4.等递归到区间只剩下一个元素才往回退.
代码:
void QuickSort(int[] array, int left, int right)
{
if(right - left > 1){
int div = partition(array, left, right);
QuickSort(array, left, div);
QuickSort(array, div+1, right);
}
}
数据分割的方法有三种:Hoare版,挖坑法,前后指针法.
3.2.1 Hoare版
算法动图演示:
来举例解释:
有一个序列为:{4,2,8,6,9,1,3,7,0,5},我们选最右侧的值作为基准值,所以key=5.
算法实现步骤: 此时以基准值5分割数据.
1.用begin标记数组首元素位置,end标记数组末尾元素. 2.begin依次往后走,找到比基准值大的元素就停下来. 3.end往前走,找到比基准值小的元素就停下来. 4.然后将begin和end位置的元素交换一下. 5.然后重复2,3,4步骤,直到begin和end相遇后,将基准值和此位置元素进行交换. 6.将此时基准值的下标返回作为分割依据,也就是此时begin的下标.供递归使用
图形演示:
此时数据被分割为以div为边界的左右两个部分.分别为[left,div),[div+1,right),div是基准值的下标 然后我们递归对div的左侧部分进行分割排序. 图形演示:
就这样一直递归着分割排序,最终得到的就是升序的序列. 代码实现如下:
public class QuickSort1 {
static void printArray(int[] array){
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]+" ");
}
}
static void swap(int[] array,int left,int right){
int temp=array[left];
array[left]=array[right];
array[right]=temp;
}
static int partition(int[] array,int left,int right){
int key=array[right-1];
int begin=0;
int end=right-1;
while (begin < end){
while (begin < end && array[begin] <= key){
begin++;
}
while (begin < end && array[end] >= key){
end--;
}
if (begin!=end){
swap(array,begin,end);
}
}
if (begin!=right-1){
swap(array,begin,right-1);
}
return begin;
}
static void quickSort(int[] array,int left,int right){
if (right-left>1){
int div=partition(array,left,right);
quickSort(array,left,div);
quickSort(array,div+1,right);
}
}
public static void main(String[] args) {
int[] array={7,6,5,8,1,3,9,2,4,0};
quickSort(array,0,array.length);
printArray(array);
}
}
3.2.2 挖坑法
算法动图演示:
来举例解释:
有一个序列为:{4,2,8,6,9,1,3,7,0,5},我们选最右侧的值作为基准值,所以key=5.
算法实现步骤: 以基准值5分割数据
1.用一个中间变量保存基准值,所以我们可以认为基准值被挖走了,所以它的位置就空出来了. 2.用begin标记数组首元素位置,end标记数组末尾元素. 3.begin依次往后走,找到比基准值大的元素就去填上一个坑.此时begin的位置就成为了一个新坑. 4.end往前走,找到比基准值小的元素就填上一个坑.此时end的位置就成为了一个新坑. 5.然后重复3,4步骤,直到begin和end相遇后,用基准值将此位置的坑一补. 6.将此时基准值的下标返回作为分割依据,也就是此时begin的下标.供递归使用
图形演示:
此时数据被分割为以div为边界的左右两个部分.分别为[left,div),[div+1,right),div是基准值的下标. 然后对左右两侧进行递归就行. 代码实现如下:
public class QuickSort2 {
static void printArray(int[] array){
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]+" ");
}
}
static void swap(int[] array,int left,int right){
int temp=array[left];
array[left]=array[right];
array[right]=temp;
}
static int partition(int[] array,int left,int right){
int key=array[right-1];
int begin=left;
int end=right-1;
while (begin <end){
while (begin<end && array[begin]<=key){
begin++;
}
if (begin<end){
array[end]=array[begin];
}
while (begin<end && array[end]>=key){
end--;
}
if (begin<end){
array[begin]=array[end];
}
}
array[begin]=key;
return begin;
}
static void quickSort(int[] array,int left,int right){
if (right-left>1){
int div=partition(array,left,right);
quickSort(array,left,div);
quickSort(array,div+1,right);
}
}
public static void main(String[] args) {
int[] array={7,6,5,8,1,3,9,2,4,0};
quickSort(array,0,array.length);
printArray(array);
}
}
3.2.3 前后指针法(不太好想,大家掌握前两种就行了)
来举例解释:
有一个序列为:{4,2,8,6,9,1,3,7,0,5},我们选最右侧的值作为基准值,所以key=5.
算法实现步骤:
1.left表示数组的左边界,用cur保存一份,用prev表示cur的前一个元素. int cur = left;int prev=cur-1; 2.right表示数组的右边界,如果array[cur]<key&& ++prev!=cur ,就交换prev和cur处的值.不管是否满足条件,cur都往后走一步. 注意:此处是++prev ,即是prev+1 后再执行语句. 3.两个条件都满足时,交换两个位置的元素,cur继续后移. 4.当array[cur]=key 时,cur=righ t跳出循环,此时判断prev++!=right-1 ,所以交换prev位置元素和right-1位置元素.返回此时prev的下标,作为下一次分割依据
代码:
int cur=left;
int prev=cur-1;
int key=array[right-1];
while (cur<right){
if (array[cur]<key && ++prev != cur){
swap(array,cur,prev);
}
cur++;
}
if (++prev!=right-1){
swap(array,prev,right-1);
}
return prev;
图形演示:
此时原来的数据就会被分割成两部分,再经过递归后就可以将数据分割排序.
代码实现如下:
public class QuickSort3 {
static void printArray(int[] array){
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]+" ");
}
}
static void swap(int[] array,int left,int right){
int temp=array[left];
array[left]=array[right];
array[right]=temp;
}
static int partition(int[] array,int left,int right){
int cur=left;
int prev=cur-1;
int key=array[right-1];
while (cur<right){
if (array[cur]<key && ++prev != cur){
swap(array,cur,prev);
}
cur++;
}
if (++prev!=right-1){
swap(array,prev,right-1);
}
return prev;
}
static void quickSort(int[] array,int left,int right){
if (right-left>1){
int div=partition(array,left,right);
quickSort(array,left,div);
quickSort(array,div+1,right);
}
}
public static void main(String[] args) {
int[] array={7,6,5,8,1,3,9,2,4,0};
quickSort(array,0,array.length);
printArray(array);
}
}
快排性能分析:
最坏场景: 如果序列有序或者接近有序,每次取到基准值如果是区间中的最大值或者最小值,那么基准值就会出现一侧有数据,一侧没有数据.因此快排不适合于有序或者接近有序的场景进行排序. 时间复杂度:O(N^2) 最优场景: 序列比较随机,数据比较凌乱:每次取基准值都比较理想. 时间复杂度:O(N*logN) 应用场景: 数据非常随机,数据非常凌乱 在这里要注意,快排一般给的时间复杂度都是O(N*logN)而不是O(N^2),所以要避免去取到极值的概率.
快速排序的特性总结:
1.快速排序整体的综合性能和使用前景都是比较好的,所以才敢叫快速排序. 2.时间复杂度:O(N*logN) 3.空间复杂度:O(logN) 4.稳定性:不稳定.
3.2.4 快速排序的优化
之前取极值的方法为了代码实现的方便,我们是从区间的最左侧或者最右侧取基准值,但是从两侧取值取到极值的概率会非常高. 所以我们要重新找一个取极值的方法. 三数取中法—>一次性取三个数: 最左侧取一个数据,最右侧取一个数据,中间再取一个数据,以这三个数据最中间的数据作为基准值. 算法实现步骤:
1.left标记数组最左侧元素,right是数组长度.mid标记中间元素. 2.三个元素进行比较,如果 array[mid]<左<右,所以left标记的是中间元素,返回left 3.如果 左<右<array[mid],(right-1)标记的是中间元素,返回(right-1). 4.如果 左<array[mid]<右,mid标记的是中间元素,返回mid.
代码实现如下:
static int getIndexOfMiddle(int[] array,int left,int right){
int mid=left+(right-left)/2;
if(array[left]<array[right-1]){
if(array[mid]<array[left]){
return left;
}else if(array[mid]>array[right-1]){
return right-1;
}else{
return mid;
}
}else{
if(array[mid]>array[left]){
return left;
}else if(array[mid]<array[right]){
return right-1;
}else{
return mid;
}
}
}
接下来就是将这块代码和上面分割的方法结合起来,我就直接用一下上面Hoare版的例子进行结合说明. 结合完的代码如下:
static int partition(int[] array,int left,int right){
int index=getIndexOfMiddle(array,left,right);
if(index!=right-1){
swap(array,index,right-1);
}
int key=array[right-1];
int begin=left;
int end=right-1;
while (begin < end){
while (begin < end && array[begin] <= key){
begin++;
}
while (begin < end && array[end] >= key){
end--;
}
if (begin!=end){
swap(array,begin,end);
}
}
if (begin!=right-1){
swap(array,begin,right-1);
}
return begin;
}
3.2.5 快排的非递归算法(这个大家了解一下就行)
在这里我们用到了栈.用栈来存储左右区间. 做法: 1.先将初始数组的左右边界,array.length,0入栈,这是分割前的第一个区间.
2.当栈不为空时,说明栈中还有区间,将数据出栈,注意是 先入后出,所以将左边界给left,右边界给right.
3.当right-left>1时,将区间[left,right)以下标div进行分割.然后将右侧区间[div+1,right)进行入栈,再将左侧区间[left,div)进行入栈.
下一次出栈时,会让left和div出栈再次进行分割后再入栈.大家可以根据代码自己模拟画一下接下来的图形.
注意:一定要先入每个区间的右侧位置.因为这是栈,是先入后出的. 4.让2,3步骤循环起来,直到栈为空.
代码实现如下:
void QuickSortNonR(int[] a, int left, int right){
Stack<Integer> st = new Stack<>();
st.push(array.length);
st.push(0);
while (!st.empty())
{
left= st.pop();
right = st.pop();
if(right - left <= 1){
continue;
}
int div = partition(a, left, right);
st.push(div+1);
st.push(right);
st.push(left);
st.push(div);
}
}
4.归并排序
基本思想: 是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用.将已经有序的子序列合并得到完全有序的序列;即先使每个子序列有序,再使子序列段之间有序. 归并排序动图演示:
归并排序的核心步骤如下: 每次对序列进行均分,均分到一定程度,发现区间中的数据有序了,再来进行合并.
分解一个序列就是上方的上半部分图形,进行均分: 原本的序列为 {7,6,5,8,1,3,9,2,4,0},有10个元素.
1.left初值为0,right初值为数组的长度,所以区间为[0,10),mid=left+(right-left)/2 2.第一次进行均分后,mid为5.左区间为[0,5)右区间为[5,10). 3.递归进去对左区间[0,5)再次进行均分,此时的区间划分为: 一直进行递归划分,就可以得到上面的那个分解阶段的图形.
当分解完成后我们如何将两个有序的序列合并成一个序列呢? 我们就用上述的某个序列,将他们合并成一个新的有序序列进行讲解. 序列1:{1,5,6,7,8},序列2;{0,2,3,4,9},此时的划分mid应该是5,所以左区间为[0,5),右区间为[5,10). 做法步骤:
1.用begin1,begin2,标记这两个序列的初始位置,用index标记新序列的初始位置 2.如果begin1处的元素大于begin2位置的元素,则将begin1处位置的元素搬移到index的位置,然后,begin1++,index++; 3.如果begin1处的元素小于begin2位置的元素,则将begin2处位置的元素搬移到index的位置,然后,begin2++,index++; 4.当某个序列的元素全部搬移完的话,进行判断看另外一个序列中是否还有元素,如果有的话相应往下搬移就行.
图形演示:
初始的时候: 第一次比较: 第二次比较: 第三次比较: 第四次比较: 第五次比较: 中间步骤省略,直接快进到最后8和9进行相比: 到这一步我们会发现,下标begin1已经超出了数组边界,我们通过判断后发现另外一个序列中还有元素,所以将另外一个序列中的元素搬移下来就行,
还有些具体的解释在下面代码中会进行指出. 代码实现如下:
public class MergeSort {
static void printArray(int[] array){
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]+" ");
}
System.out.println();
}
static void mergeData(int[] array, int left, int mid, int right, int[] temp){
int begin1=left,end1=mid;
int begin2=mid,end2=right;
int index=left;
while (begin1<end1 && begin2<end2){
if (array[begin1]<=array[begin2]){
temp[index]=array[begin1];
index++;
begin1++;
}else{
temp[index]=array[begin2];
index++;
begin2++;
}
}
while (begin1<end1){
temp[index]=array[begin1];
index++;
begin1++;
}
while (begin2<end2){
temp[index]=array[begin2];
index++;
begin2++;
}
}
private static void mergeSort(int[] array,int left,int right,int[] temp){
if (right-left>1){
int mid=left+(right-left)/2;
mergeSort(array,left,mid,temp);
mergeSort(array,mid,right,temp);
mergeData(array,left,mid,right,temp);
System.arraycopy(temp,left,array,left,right-left);
}
}
static void mergeSort(int[] array){
int[] temp=new int[array.length];
mergeSort(array,0,array.length,temp);
}
public static void main(String[] args) {
int[] array={7,6,5,8,1,3,9,2,4,0};
mergeSort(array);
printArray(array);
}
}
归并排序的特性总结:
1.归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘外的外排序问题. 2.时间复杂度:O(N*logN) 3.空间复杂度:O(N) 4.稳定性:稳定
5.计数排序
基本思想:
先统计每个元素出现的次数,然后根据计数的结果对数据进行回收
算法实现步骤:
1.通过遍历先去找序列中的最大值(maxValue )和最小值(minValue ),去确定数据的范围(range=maxValue-minValue+1 ) 2.创建一个长度为range 的数组去统计array中每个数据出现的次数. 3.根据统计的结果去回收数据
我们给一组序列 {4,2,3,9,4,7,0,1,1,9,8,2,5,6,4,3} 图形演示:
1.通过计算,range=9-0+1=10,所以我们需要创建一个长度为10的统计数组. 数组中存的是元素的出现次数,初始时出现次数都为0.2.通过遍历开始统计每个元素出现的次数:count[array[i]]++; 当array[0]=4 时,count[4]++ = 1; 当array[1]=2 时,count[2]++ = 1; 当array[2]=3 时,count[3]++ = 1; 当array[3]=9 时,count[9]++ = 1 ; 当array[4]=4 时,count[4]++ = 2; 当array[5]=7 时,count[7]++ = 1 ; 当array[6]=0 时,count[0]++ = 1; 当array[7]=1 时,count[1]++ = 1 ; 当array[8]=1 时,count[1]++ = 2; 当array[9]=9 时,count[9]++ = 2 ; 当array[10]=8 时,count[8]++ = 1; 当array[11]=2 时,count[2]++ = 2 ; 当array[12]=5 时,count[5]++ = 1; 当array[13]=6 时,count[6]++ = 1 ; 当array[14]=4 时,count[4]++ = 3; 当array[15]=3 时,count[9]++ = 2 ; 经过统计: 0出现了1次,1出现了2次,2出现了2次,3出现了2次,4出现了3次. 5出现了1次,6出现了1次,7出现了1次,8出现了1次,9出现了2次. 3.根据计数的结果来回收数据,从count数组下标由小到大进行回收. 将count中的数据遍历出来依次往array中放,覆盖它原有的数据
代码实现如下:
public class CountSort{
static void printArray(int[] array){
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]+" ");
}
System.out.println();
}
public static void countSort(int[] array){
int maxValue=array[0];
int minValue=array[0];
for(int i=0;i<array.length;i++){
if(array[i]>maxValue){
maxValue=array[i];
}
if(array[i]<minValue){
minValue=array[i];
}
}
int range=maxValue-minValue+1;
int[] count=new int[range];
for(int i=0;i<array.length;i++){
count[array[i]-minValue]++;
}
int index=0;
for(int i=0;i<range;i++){
while(count[i]>0){
array[index++]=i+minValue;
count[i]--;
}
}
}
public static void main(String[] args) {
int[] array={4,2,3,9,4,7,0,1,1,9,8,2,5,6,4,3};
countSort(array);
printArray(array);
}
}
计数排序的特性总结:
1.基数排序在数据范围集中时,效率很高,但是适用范围和场景有限. 2.时间复杂度:O(MAX(N,范围)) 3.空间复杂度:O(范围) 4.稳定性:稳定
有什么错误或者有什么需要优化的地方,欢迎各位大佬来指正!!!
|