Java数组08:八大排序方法(未完待续)

八大排序

  • 直接插入排序
    • 1、思想:直接插入排序是一种简单的插入排序法,基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。比如玩扑克牌时,整理扑克就用了这个思想。
    • 2、实现:当插入第(i>=1)个元素时,前面的array[0],array[1]…array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2]…的排序码顺序进行比较,找到插入位置将array[i]插入,原来位置行的元素顺序后移
      Java数组08:八大排序方法(未完待续)_第1张图片
    • 代码实现:
void InsertSort(int *num, int len)
{
    int i, j, temp;
    for (i = 1; i < len; i++)
    {
        // num[i]之前的序列是有序的,num[i - 1]为有序部分的最大值
        // 故只需要与有序部分的最大值进行比较,即可判断是否需要插入
        if (num[i - 1] > num[i])
        {
            // 获取需要插入的数据
            temp = num[i];         
            // 依次后移,查找插入位置
            for ( j = i - 1; num[j] > temp && j >= 0; j--)
            {
                num[j + 1] = num[j];
            }
            // 完成插入
            num[j + 1] = temp;  
        } 
    }
}

  特性:元素集合越接近有序,直接插入排序算法效率的时间效率越高;最好O(N)顺序有序,最坏O(N^2)接近有序;空间复杂度O(1)。
  • 希尔排序
    • 1、思想:希尔排序法又称缩小增量法。希尔排序法的基本思想是:step1先选定一个整数gap
    • 实现

      Java数组08:八大排序方法(未完待续)_第2张图片
      Java数组08:八大排序方法(未完待续)_第3张图片
    • 代码
int shsort(int s[], int n)    /* 自定义函数 shsort()*/
{
    int i,j,d;
    d=n/2;    /*确定固定增虽值*/
    while(d>=1)
    {
        for(i=d+1;i<=n;i++)    /*数组下标从d+1开始进行直接插入排序*/
        {
            s[0]=s[i];    /*设置监视哨*/
            j=i-d;    /*确定要进行比较的元素的最右边位置*/
            while((j>0)&&(s[0]<s[j]))
            {
                s[j+d]=s[j];    /*数据右移*/
                j=j-d;    /*向左移d个位置V*/
            }
            s[j + d]=s[0];    /*在确定的位罝插入s[i]*/
        }
        d = d/2;    /*增里变为原来的一半*/
    }
return 0;
}
 特性:希尔排序是对直接插入排序的优化(先分组,对分组对数据进行插入排序);当gap>1的时候都是预排序,目的是让数组更接近于有序。当gap==1时,数组已经接近有序了;时间复杂度:O(N^1.3) ~ O(N^2)      空间复杂度 : O(1)
  • 选择排序
    • 思想:每一次从待排序列元素中选出最小(或最大)对一个元素,存放在序列的起始(最后)位置,直到全部待排序的数据元素排完。
    • 实现:step1在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素;step2若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换;step3在剩余的array[i]–array[n-2] or(array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素

Java数组08:八大排序方法(未完待续)_第4张图片

  • 代码
void SelectSort(int* a, int n)
{
   int left = 0, right = n - 1;
   while (left < right)
   {
   	// 选出最大的值和最小的值
   	int minIndex = left, maxIndex = left;
   	for (int i = left; i <= right; ++i)
   	{
   		if (a[i] < a[minIndex])
   			minIndex = i;

   		if (a[i] > a[maxIndex])
   			maxIndex = i;
   	}

   	Swap(&a[left], &a[minIndex]);

   	// 如果max和left位置重叠,max被换走了,要修正一下max的位置  !!!
   	if (left == maxIndex)
   	{
   		maxIndex = minIndex;
   	}

   	Swap(&a[right], &a[maxIndex]);
   	++left;
   	--right;


   }
 特性:①直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用;②时间复杂度:O(N^2)   空间复杂度 : O(1)
  • 堆排序
    • 思想:堆事一种数据结构,一种叫做完全二叉树的数据结构。堆排序是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据,需要注意的是排升序要建大堆,排降序要建小堆。
    • 实现:
      堆排序的实现有一个重要的算法: 向下调整算法( 用于建大堆 )

① 从根结点处开始,选出左右孩子中值较大的孩子下标。
② 让值较大的孩子与其父亲进行比较。
③ 如果值孩子比父亲大,将父亲的值 和 孩子的值交换 ,并将原来值较大的孩子的位置当成父亲,循环到 ① 继续向下进行调整,直到调整到叶子结点为止。如果值孩子比父亲小,则不需处理了,调整完成,整个树已经是大堆了。

堆排序的前提是有一个堆。

(1) 如何建堆? 利用向下调整算法将原数组调整为一个大堆。

Java数组08:八大排序方法(未完待续)_第5张图片
2) 如何进行堆排序呢?
 1、将堆顶数据与堆的最后一个数据交换,此时产生一个新的堆,不包含交换到最后位置的那一个数据,然后从堆顶位置进行一次向下调整。
 2、经过步骤 1 堆中最大的数据又位于堆顶,循环执行步骤 1 ,每次把堆中的最大数据与堆的最后一个数据进行交换,以此类推就形成了一个有序的序列。

Java数组08:八大排序方法(未完待续)_第6张图片

  • 代码实现
void AdjustDwon(int* a, int n, int root)
{
	int parent = root;
	int child = parent * 2 + 1;
 
	while (child < n) //一直向下调整
	{
		if (child + 1 < n && a[child + 1] > a[child]) //child防止越界 找最大child的下标
		{
			child++;
		}
 
		if (a[parent] < a[child]) 
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else //没有比parent大的结束
		{
			break;
		}
 
 
	}
}
 
void HeapSort(int* a, int n)
{
	//刚开始自底向上调整 建堆  - 从倒数第二层开始
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDwon(a, n, i);
	}
 
 
	int end = n - 1; //依次从堆顶拿到最大值放到后面
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDwon(a, end, 0);
		--end;
	}
}

特性分析:
①堆的向下调整算法的时间复杂度: 最坏的情况下(即一直需要交换结点),需要循环的次数为:h - 1次(h为树的高度)。而h = log2(N + 1) (N为树的总结点数)。所以堆的向下调整算法的时间复杂度为:O(logN) 。
②建堆的时间复杂度 : 假设高度为k,则从倒数第二层右边的节点开始,这一层的节点都要执行子节点比较然后交换(如果顺序是对的就不用交换);倒数第三层呢,则会选择其子节点进行比较和交换,如果没交换就可以不用再执行下去了。如果交换了,那么又要选择一支子树进行比较和交换;高层也是这样逐渐递归。
  那么总的时间计算为:s = 2^( i - 1 ) * ( k - i );其中 i 表示第几层,2^( i - 1) 表示该层上有多少个元素,( k - i) 表示子树上要下调比较的次数。
  S = 2^(k-2) * 1 + 2(k-3)2……+2(k-2)+2(0)*(k-1) ===> 因为叶子层不用交换,所以i从 k-1 开始到 1;
  S = 2^k -k -1;又因为k为完全二叉树的深度,而log(n) =k,把此式带入;
  得到:S = n - log(n) -1,所以时间复杂度为:O(n)
③排序重建堆: 在取出堆顶点放到对应位置并把原堆的最后一个节点填充到堆顶点之后,需要对堆进行重建,只需要对堆的顶点调用AdjustDown() 函数。
  每次重建意味着有一个节点出堆,所以需要将堆的容量减一。AdjustDown() 函数的时间复杂度k=log(n),k为堆的层数。所以在每次重建时,随着堆的容量的减小,层数会下降,函数时间复杂度会变化。重建堆一共需要n-1次循环,每次循环的比较次数为log(i),则相加为:log2+log3+…+log(n-1)+log(n)≈log(n!)。可以证明log(n!)和nlog(n)是同阶函数:
∵(n/2)n/2≤n!≤nn,
∴n/4log(n)=n/2log(n1/2)≤n/2log(n/2)≤log(n!)≤nlog(n)
所以时间复杂度为O(nlogn)
④小结 : 初始化建堆的时间复杂度为O(n),排序重建堆的时间复杂度为nlog(n),所以总的时间复杂度为O(n+nlogn)=O(nlogn)。另外堆排序的比较次数和序列的初始状态有关,但只是在序列初始状态为堆的情况下比较次数显著减少,在序列有序或逆序的情况下比较次数不会发生明显变化。
⑤空间复杂度: O(1)

  • 冒泡排序
    • 思想:所谓交换,就是根据序列中两个记录健值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将健值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
    • 实现:
      Java数组08:八大排序方法(未完待续)_第7张图片
  • 代码实现
package com.kuang.array;

import java.util.Arrays;

public class ArrayDemo07 {
    public static void main(String[] args) {
        int[] a = {1,4,5,6,7,8,10};

        System.out.println(Arrays.toString(sort(a)));
        System.out.println(Arrays.toString(a));
    }
    //冒泡排序
    //1、比较数组中,两个相邻的元素,如果第一个数比第二个数大,我们就交换他们的位置
    //2、每一次比较,都会产生一个最大,或者最小的数字;
    //3、下一轮则可以少一次排序!
    //4、依次循环,直到结束!
    public static int[] sort(int[] array){
        int temp = 0;
        //外层循环,判断我们这个要走多少次;
        for (int i = 0;i < array.length-1;i++){
            boolean flag = false;//通过flag标识位减少没有意义大比较
            //内层循环,比较判断两个数,如果第一个数比第二个数大则交换他两的位置
            for (int j=0;j < array.length-1-i;j++){
                if (array[j+1]>array[j]){
                    temp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = temp;
                    flag = true;
                }
            }
            if (flag == false){
                break;
            }
        }
        return array;
    }
}

特性总结:
① 时间复杂度: O(N^2) 空间复杂度: O(1)
② 冒泡和插入相比谁更好? 顺序有序一样好,接近有序插入好

  • 快速排序
    • 思想:快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
    • 实现 :
      虽然快速排序称为分治法,但分治法这三个字显然无法很好的概括快速排序的全部步骤。因此我的对快速排序作了进一步的说明:挖坑填数+分治法:
      先来看实例吧,定义下面再给出(最好能用自己的话来总结定义,这样对实现代码会有帮助)。以一个数组作为示例,取区间第一个数为基准数。请添加图片描述
      初始时,i = 0; j = 9; X = a[i] = 72
      由于已经将 a[0] 中的数保存到 X 中,可以理解成在数组 a[0] 上挖了个坑,可以将其它数据填充到这来。
      从j开始向前找一个比X小或等于X的数。当j=8,符合条件,将a[8]挖出再填到上一个坑a[0]中。a[0]=a[8]; i++; 这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个坑。这次从i开始向后找一个大于X的数,当i=3,符合条件,将a[3]挖出再填到上一个坑中a[8]=a[3]; j–;
      请添加图片描述
      i = 3; j = 7; X=72
      再重复上面的步骤,先从后向前找,再从前向后找。
      从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++;
      从i开始向后找,当i=5时,由于i==j退出。
      此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将X填入a[5]。
      数组变为:
      请添加图片描述
      可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。
总结

1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。
2.j–由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中。

  • 代码
    1、挖坑填数的代码:
int AdjustArray(int s[], int l, int r) //返回调整后基准数的位置
{
    int i = l, j = r;
    int x = s[l]; //s[l]即s[i]就是第一个坑
    while (i < j)
    {
        // 从右向左找小于x的数来填s[i]
        while(i < j && s[j] >= x) 
            j--;  
        if(i < j) 
        {
            s[i] = s[j]; //将s[j]填到s[i]中,s[j]就形成了一个新的坑
            i++;
        }
 
        // 从左向右找大于或等于x的数来填s[j]
        while(i < j && s[i] < x)
            i++;  
        if(i < j) 
        {
            s[j] = s[i]; //将s[i]填到s[j]中,s[i]就形成了一个新的坑
            j--;
        }
    }
    //退出时,i等于j。将x填到这个坑中。
    s[i] = x;
 
    return i;
}

2、分治法的代码

void quick_sort1(int s[], int l, int r)
{
    if (l < r)
    {
        int i = AdjustArray(s, l, r);//先成挖坑填数法调整s[]
        quick_sort1(s, l, i - 1); // 递归调用 
        quick_sort1(s, i + 1, r);
    }
}

整合:

//快速排序
void quick_sort(int s[], int l, int r)
{
    if (l < r)
    {
        //Swap(s[l], s[(l + r) / 2]); //将中间的这个数和第一个数交换 参见注1
        int i = l, j = r, x = s[l];
        while (i < j)
        {
            while(i < j && s[j] >= x) // 从右向左找第一个小于x的数
                j--;  
            if(i < j) 
                s[i++] = s[j];
            
            while(i < j && s[i] < x) // 从左向右找第一个大于等于x的数
                i++;  
            if(i < j) 
                s[j--] = s[i];
        }
        s[i] = x;
        quick_sort(s, l, i - 1); // 递归调用 
        quick_sort(s, i + 1, r);
    }
}
非递归法

为什么要有非递归的方法?
递归 现代编译器优化很好,性能已经不是大问题
最大的问题->递归深度太深,程序本身没问题,但是栈空间不够,导致栈溢出
只能改成非递归,改成非递归有两种方式:
1、直接改循环
2、树遍历非递归和快排非递归等等,只能用Stack存储数据模拟递归过程

快速排序的非递归思路:
 1、先将待排序列的左右边界的下标入栈。
 2、当栈不为空时,读取栈中的数据(分别读取到一段区间的 left ,right 边界),然后调用某一版本的单趟排序,排序之后返回 key 的下标,然后判断key的左右区间是否还有数据( 数据个数 > 1),如果还有数据则将相应区间的left 和 right 边界 入栈,否则就不需要排序了。
 3、循环执行执行步骤2,直到栈为空为止,此时整个区间就有序了。

void QuickSortNonR(int* a, int begin, int end)
{
	stack<int> st; //辅助栈
	st.push(begin); //将一个范围的左右边界入栈
	st.push(end);
 
	while (!st.empty())
	{
		int left, right;
		right = st.top(); //获取左右边界
		st.pop();
 
		left = st.top();
		st.pop();
 
		int keyi = PartSort1(a, left, right); //对这个区间进行单趟排序
		if (left < keyi - 1)  //将排序完的数据再进行划分,划分为左右两个区间
		{                     //将左右两个区间的 ,左右边界分别入栈
			st.push(left);
			st.push(keyi - 1);
		}
 
		if (keyi + 1 < right)
		{
			st.push(keyi + 1);
			st.push(right);
		}
	}
 
}
快速排序的两个优化:
  • 三数取中

Java数组08:八大排序方法(未完待续)_第8张图片
分析:

最理想的情况下:如果每趟排序所选的key正好是该序列有序时的中间值,那么一趟排序之后key就位于序列正中间,此时的快速排序的时间复杂度就是O(NlogN)。

最差的情况下:当待排序列本就是一个有序的序列 或者 接近有序时,如果仍然选择最左边或者最右边的数作为key,那么快速排序的效率将达到最低 ,O(N^2).

为了防止这种极端情况对我们效率的影响,于是出现了三数取中来进行优化:
  三数取中指的是:最左边的数、最右边的数以及中间位置的数,我们取中三个数的中间值做key, 将这个值放到最左边 或者 最右边,这就确保了我们所选取的数不会是序列中的最大或是最小值了。

int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) >> 1;
	// left  mid  right
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else // a[left] > a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}
  • 2.小区间优化
    ① 我们可以看到,即使是上面理想状态下的快速排序,也不能避免随着递归的深入,每一层的递归次数会以2倍的形式快速增长。

② 为了减少递归树的最后几层递归,我们可以设置一个判断语句,当序列的长度小于某个数的时候就不再进行快速排序,转而使用其他种类的排序。小区间优化若是使用得当的话,会在一定程度上加快快速排序的效率,而且待排序列的长度越长,该效果越明显。

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
 
	// 1、如果这个子区间是数据较多,继续选key单趟,分割子区间分治递归
	// 2、如果这个子区间是数据较小,再去分治递归不太划算
 
	if ((end - begin) > 10) //个数 > 10 使用递归   一般给 10 ~ 20,现在编译器对递归的优化已经很好了
	{
		int keyi = PartSort3(a, begin, end);
 
 
		//[begin ,keyi - 1] keyi [keyi+1 , end]  子区间   //相当于前序
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
	else  //个数 <= 10 再使用递归消耗函数栈帧比较大
	{
		InsertSort(a + begin, end - begin + 1); //这里可以灵活修改,针对某个场景进行优化
	}
 
}

  • 归并排序
    • 思想:归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
      Java数组08:八大排序方法(未完待续)_第9张图片
    • 代码:

合并两组有序序列

归并排序就是不断的将两组有序的序列进行合并(第一次将单独的一个元素当作一组有序序列),从而得到最终的排序结果。所以首先给出如下一段代码,用来合并两组有序的序列(排升序):

void MergeData(int* arr,int left,int mid,int right,int* s){//对两组有序序列进行归并
    int begin1=left;//第一组序列的首元素下标
    int end1=mid-1;//第一组序列最后元素下标
    int begin2=mid;//第二组序列首元素下标
    int end2=right-1;//第二组序列最后元素下标
    int count=left;//进行归并时第一个元素应放在对应的位置
    while(begin1<=end1&&begin2<=end2){//归并两组序列
        if(arr[begin1]<=arr[begin2]){
            s[count]=arr[begin1];
            begin1++;
        }else{
            s[count]=arr[begin2];
            begin2++;
        }
        count++;
    }
    while(begin1<=end1){//如果第一组序列有剩余,继续归并
        s[count]=arr[begin1];
        count++;
        begin1++;
    }
    while(begin2<=end2){//如果di二组序列有剩余,继续归并
        s[count]=arr[begin2];
        count++;
        begin2++;
    }
}

以上代码中,因为合并时数字都在同一数组当中,所以它们的下标是连续的,在合并时需要使用新的空间来放合并后的结果,注意:合并后的结果应该放在新申请出的空间的对应位置,即count必须从left开始,因为在以下代码中需要拷贝。

归并排序

  • 1、递归实现:不断地将区间一半一半地划分,直到只剩下一个数据时开始往回合并,利用一个temp数组辅助我们将两段区间合并,最后合并为一个有序区间。
void MergeSort(int* arr,int left,int right,int* s){//归并排序递归
    if(right-left<=1){//元素个数小于1,直接返回
        return;
    }
    int mid=left+(right-left)/2;
    MergeSort(arr,left,mid,s);//归并左半部分
    MergeSort(arr,mid,right,s);//归并右半部分
    MergeData(arr,left,mid,right,s);//将当前有序的序列进行归并
    memcpy(arr+left,s+left,sizeof(arr[0])*(right-left));//将归并后的将结果拷贝回原来的数组,为下次归并做准备
 
}

上述递归过程中不断将问题的规模缩小,直到对两个元素进行排序,而后在层层处理其余部分。归并排序的递归格式有点像二叉树的后序遍历,很方便记忆。注意:每次归并完一组数据,都要把归并好的结果拷贝回原来的空间,还要注意当前拷贝的位置与拷贝的元素个数。

  • 2、非递归实现:使用循环的方式来进行合并,我们每次控制参与合并的元素个数,按照 2的次方方式 使 gap 逐渐增加 ,但是gap要满足 gap < n.

Java数组08:八大排序方法(未完待续)_第10张图片
图片中的例子只是一种理想的情况,在这里我们还会遇到三种需要特殊处理的情况:

  • 情况1:当最后一个小组归并时,第一个小区间不够gap个,就不需要归并了。
    Java数组08:八大排序方法(未完待续)_第11张图片
  • 情况2:当最后一个小组归并时,第二个小区间存在,第二个区间不够gap个,需要调整边界。
    Java数组08:八大排序方法(未完待续)_第12张图片
  • **情况3:当最后一个小组归并时,第二个小区间不存在,不需要归并了。 **
    Java数组08:八大排序方法(未完待续)_第13张图片
    • 代码
void MergeSortNonR(int* arr,int size){
    int* s=(int*)malloc(sizeof(arr[0])*size);
    int gap=1;//相当于第一次每一个为一组
    while(gap<size){
        int i=0;
        for(;i<size;i+=2*gap){
            int left=i;
            int mid=left+gap;
            if(mid>size){//越界,进行复位
                mid=size;
            }
            int right=mid+gap;
            if(right>size){
                right=size;
            }
            MergeData(arr,left,mid,right,s);
        }
        gap*=2;
        memcpy(arr,s,sizeof(arr[0])*size);//拷贝
    }
}

当gap=1时,此时每两个元素一组,把这两个元素进行了排序,然后不断将数组元素划分为两个一组,全部排序。到下一次gap就会增长2倍,此时四个元素一组,重复上述过程。直到gap>size,说明整个归并排序结束。注意:此时的拷贝只需要在gap变化之后进行整体拷贝。
特性总结:
**1.时间复杂度:O(N*logN) **
Java数组08:八大排序方法(未完待续)_第14张图片

排序的过程像是一颗二叉树,每一层表示归并这层元素,复杂度为O(N),深度为logN,所以整体的时间复杂度为:O(NlogN)
2、空间复杂度:O(N)
每次在堆上申请的空间大小与原数组相同,所以空间复杂度为:O(N)

2.归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题

完整代码

#include 
#include 
#include 
void MergeData(int* arr,int left,int mid,int right,int* s){//对两组有序序列进行归并
    int begin1=left;//第一组序列的首元素下标
    int end1=mid-1;//第一组序列最后元素下标
    int begin2=mid;//第二组序列首元素下标
    int end2=right-1;//第二组序列最后元素下标
    int count=left;//进行归并时第一个元素应放在对应的位置
    while(begin1<=end1&&begin2<=end2){//归并两组序列
        if(arr[begin1]<=arr[begin2]){
            s[count]=arr[begin1];
            begin1++;
        }else{
            s[count]=arr[begin2];
            begin2++;
        }
        count++;
    }
    while(begin1<=end1){//如果第一组序列有剩余,继续归并
        s[count]=arr[begin1];
        count++;
        begin1++;
    }
    while(begin2<=end2){//如果di二组序列有剩余,继续归并
        s[count]=arr[begin2];
        count++;
        begin2++;
    }
}
void MergeSort(int* arr,int left,int right,int* s){//归并排序递归
    if(right-left<=1){//元素个数小于1,直接返回
        return;
    }
    int mid=left+(right-left)/2;
    MergeSort(arr,left,mid,s);//归并左半部分
    MergeSort(arr,mid,right,s);//归并右半部分
    MergeData(arr,left,mid,right,s);//将当前有序的序列进行归并
    memcpy(arr+left,s+left,sizeof(arr[0])*(right-left));//将归并后的将结果拷贝回原来的数组,为下次归并做准备
 
}
void MergeSortNonR(int* arr,int size){
    int* s=(int*)malloc(sizeof(arr[0])*size);
    int gap=1;//相当于第一次每一个为一组
    while(gap<size){
        int i=0;
        for(;i<size;i+=2*gap){
            int left=i;
            int mid=left+gap;
            if(mid>size){//越界,进行复位
                mid=size;
            }
            int right=mid+gap;
            if(right>size){
                right=size;
            }
            MergeData(arr,left,mid,right,s);
        }
        gap*=2;
        memcpy(arr,s,sizeof(arr[0])*size);//拷贝
    }
}
void Print(int* arr,int size){
    int i=0;
    for(;i<size;i++){
        printf("%d ",arr[i]);
    }
    printf("\n");
}
int main(){
    int arr[]={5,7,1,3,9,8,0,2,4,6};
    //int* s=(int*)malloc(sizeof(arr));
    //MergeSort(arr,0,sizeof(arr)/sizeof(arr[0]),s);
    MergeSortNonR(arr,sizeof(arr)/sizeof(arr[0])); 
    Print(arr,sizeof(arr)/sizeof(arr[0]));
    return 0;
}

  • 计数排序

你可能感兴趣的:(Java学习,java)