排序算法——全

堆排序

时间复杂度:nlogn
堆的条件

  1. 完全二叉树(了解可以看笔者另外一篇关于二叉树的解读)
    二叉树的概念
  2. 父节点大于子节点 or 小于子节点
    大于子节点的堆,叫大根堆,反之则为小根堆

堆排序的核心就两步,这里以大根堆为例子
第一步,建堆
第二步,排序

建堆

  1. 我们从最后一个节点的父节点开始,从右往左,然后往上的顺序,这样子可以保证,下边的树,都是堆。

在这里插入图片描述

  1. 父节点 子节点 中找到最大值,然后交换
    这里需要注意的是,假设我们当前节点为i,这里是以0开始的数组
    parent = (i - 1) / 2
    c1 = 2 * i + 1
    c2 = 2 * i + 2
    在这里插入图片描述

排序
建完堆后,根节点就是最大值,与最后一个节点交换,然后下一趟排序的范围就少一个值

需要注意的是,我们这里是从最后一个节点的父节点开始的
在这里插入图片描述
为什么要从这里开始呢,实际上就是为了省下一些没必要的操作,因为叶子节点没必要进行heapify

 public static void heapSort1(int[] data)
    {
        //排序的次数等于长度 - 1,例如长度为5,那么只用四次就可以找到4个最大值,那么最后一个值,一定比其他四个值小
        for(int i = 0; i < data.length - 1; i++)
        {
            heapify1(data,data.length - i - 1);    //建堆,这里有边界,每进行一次建堆,范围就得小一个值,那么刚好可以用趟数减去
            swap(data,0,data.length - i - 1);      //交换最后一个值
        }
    }

    private static void heapify1(int[] data, int right) {
        //找到叶子节点
        int parent = (right - 1) / 2;
        //开始做循环,循环结束的条件就是到根节点
        while(parent >= 0)
        {
            int c1 = parent * 2 + 1;   //左节点
            int c2 = parent * 2 + 2;   //右节点
            int max = parent;
            if(c1 <= right && data[c1] > data[max]) //这里需要注意左右节点的越界问题
            {
               max = c1;
            }
            if(c2 <= right && data[c2] > data[max])
            {
                max = c2;
            }
            swap(data,max,parent);
            parent--;
        }
    }

补充

堆实际上就是个线性结构,这里再使用链表来表示堆,它的基本运算有三个

  1. void push(E e) 插入元素
  2. E pop() 删除一个元素并返回该元素
  3. boolean empty() 判断是否为空
    现在,为了简单,假设元素类型为RecType, 用 R[1…n]存放堆中的元素

插入算法

public void push(RecType e)
{
	n++;
	R[n] = e;
	if(n == 1) return;
	int j = n, i = j / 2;
	while(true)
	{	
		if(R[j].key > R[i].key)
		{
			swap(i.j);
		}
		ifi(i == 1) break;
		j = i; i = j / 2;
	}
}

这里的算法,其实十分简单,我们插入一个元素,只用把插入的地方的父节点,一直到根节点,这样就实现了。

这里的插入方法,也可以使用到堆排序的方法里边

public static void heapSort1(int[] data)
    {
        //排序的次数等于长度 - 1,例如长度为5,那么只用四次就可以找到4个最大值,那么最后一个值,一定比其他四个值小
        for(int i = 0; i < data.length - 1; i++)
        {
            heapify2(data,data.length - i - 1);    //建堆,这里有边界,每进行一次建堆,范围就得小一个值,那么刚好可以用趟数减去
            swap(data,0,data.length - i - 1);      //交换最后一个值
        }
    }
private static void heapify2(int[] data, int right)
    {
        for(int n = 1; n <= right; n++)
        {
            int j = n;   //当前节点
            int i = (j - 1) / 2;  //父节点
            while(true)
            {
                if(data[j] > data[i])    //如果大于的话,就交换
                {
                    swap(data,j,i);
                }
                if(i == 0) break;   //到达跟节点,结束循环
                j = i;              //j 指向父节点,继续往上修正堆
                i = (j - 1) / 2;    //i 指向j的父节点

            }
        }
    }

归并排序

自底向上的二路归并排序

private static void MergeSort(int[] data,int low,int right)
    {
        if(low < right)
        {
            int mid = (low + right) / 2;    //中间节点
            MergeSort(data,low,mid);   //对左边开始排序
            MergeSort(data,mid + 1,right);  //对右边开始排序
            Merge(data,low,mid,right);    //合并
        }
    }

    public static void Merge(int[] data,int low,int mid,int high)
    {
        //需要注意的是,这个方法的前提是,low到mid是有序的,mid + 1到high也是有序的
        int[] newData = new int[high - low + 1];    //新的数组
        int i = low; //i指向low
        int j = mid + 1; //j指向第二段的第一个开头
        int k = 0;    //新数组的指针
        while(i <= mid && j <= high)    //两边都要做限制
        {
            if(data[i] <= data[j])
            {
                newData[k++] = data[i++];
            } else {
                newData[k++] = data[j++];
            }
        }
        while(i <= mid)
        {
            newData[k++] = data[i++];
        }
        while(j <= high)
        {
            newData[k++] = data[j++];
        }
        for(int n = 0,m = low; n < newData.length; n++,m++)
        {
            data[m] = newData[n];  //复制回数组
        } 
    }

归并排序时间复杂度

对于归并排序,主要是,两次分,一次合

T(n) = 2T(n / 2) + O(n) (n > 1)

在这里插入图片描述

我们使用主定理法

在这里插入图片描述
带入公式,就可以得到答案

快速排序

public static void qSort(int[] data,int l, int r)
    {
        if(l < r)
        {
            int q = partition(data,l,r);
            qSort(data,l,q - 1);
            qSort(data,q + 1, r);
        }
    }

    private static int partition(int[] data, int l, int r) {
        int i = l;
        int j = r + 1;

        int x = data[l];
        while(true)
        {
            while(data[++i] < x && i < r); //这里小于r是因为,这里++i边界其实是r - 1,如果小于等于r的话,就会数组越界
            while(data[--j] > x);  //这里不用作数组越界是因为,j到最左边,到头部元素的话,不可能再往左走,所以就可以省略
            if(i >= j)break;
            swap(data,i,j);
        }
        //交换data[j]和头部元素
        data[l] = data[j];
        data[j] = x;
        return j;
    }

快速排序时间复杂度

最坏情况就是,例如 9 8 7 6 5 4 3 2 1
每次划分都是左边有一个,右边 n - 1
T(n) = T(n - 1)+ O(n)
这里求得时间复杂度得用数学方法
T(n) = T(n - 1) + O(n)
T(n - 1) = T(n - 2) + O(n)

T(2) = T(1) + O(n)
那么显然,时间复杂度为 O(n^2)

最好情况就是,每次划分都在中间,每次都分为两段一样长的
T(n) = 2T(n / 2) + O(n)
a = 2, b = 2,k =1 --> a = b^k 由主定理法
时间复杂度为 O(nlogn)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

憨憨小江

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值