堆排序(C#)

堆排序(C#)

逻辑结构:完全二叉树结构
在这里插入图片描述
实际结构实现:把数组从0出发连续一段可对应成完全二叉树
    例如数组{0,1,2,3,4,5}
    变成堆为
在这里插入图片描述
对于堆中一个节点的位置序号
在这里插入图片描述
大根堆:每一个节点为头的子树,子树上整体最大值为头节点
小根堆:每一个节点为头的子树,子树上整体最小值为头节点

在这里插入图片描述

堆的重要操作
【HeapInsert】

用户输入一组数字,怎样确保用户输入最后是一个大根堆
例:用户输入5,3,6,7…
思路:定义heapSize=0;定义一个数组arr[]存放大根堆
(1)输入5,heapSize++;
(2)输入3,heapSize++,让3与父节点【(i-1)/2】进行比较
            若小于等于父节点,不变
            若大于父节点,交换。
(3)输入6,heapSize++,让6父节点【(i-1)/2】进行比较
(4)输入7,heapSize++,让7父节点【(i-1)/2】进行比较,
           若小于等于父节点,不变
           若大于父节点,交换。然后再计算交换位置后是否有父节点,若有则继续比较,直到小于等于父节点或没有父节点为止。
在这里插入图片描述
代码实现:

public void HeapInsert(int[] arr)
{
    int heapSize = 1;
    int index = 1;
    for (int i = 1; i < arr.Length; i++)
    {
        index = i;
        while (arr[index] > arr[(index - 1) / 2])
        {
            Swap(arr,index,(index - 1) / 2);
            index=(index - 1) / 2;
        }
        heapSize++;
    }
}
private void Swap(int[] arr, int i, int j)
{
    int index = arr[i];
    arr[i] = arr[j];
    arr[j] = index;
}

【Heapify】
用户输入结束(输入已经为大根堆),要求返回最大数并去掉,同时保证剩下的仍然是一个大根堆
(1)返回数组0位置上的数即可
(2)去掉最大数后,确保剩下的数仍然是大根堆做法
         将已形成的堆结构最后一个数字放到0位置上
          heapSize-1
         从头节点开始,现在左右孩子中找最大值,再与头节点进行比较。
         若大于头节点,交换头节点与最大值孩子的位置。交换后原头节点位置下若仍有子孩子,继续像之前一样比较,直到没有子孩子或大于子孩子。
         若小于头节点,则结束。
在这里插入图片描述
代码实现:

public void Heapify(int[] arr)
    {
        Swap(arr,0,arr.Length-1);
        int heapSize = arr.Length - 1;
        int i = 0;
        int max = 0;
        int left = 2 * i + 1;
        while (heapSize > left)//下方有孩子的时候
        {
            //left + 1 < heapSize 有右孩子
            int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;//如果有右孩子,先左右孩子比较,若没有largest为左孩子
            largest = arr[largest] > arr[i] ? largest : i;//找到的最大的孩子与该结点相比较
            if (largest == i)//如果最大的为该节点,停止比较
                break;
            Swap(arr,largest,i);
            i = largest;//继续顺着改变的结点向下比较
            left = 2 * i + 1;
        }
    }

    private void Swap(int[] arr, int i, int j)
    {
        int index = arr[i];
        arr[i] = arr[j];
        arr[j] = index;
    }
}

拓展:
对于一个堆区域为0~heapSize-1
用户要求改变i位置的值a,此时怎么保证改变后仍然是大根堆?
思路:判断改变后的a值时变大还是变小
         若是变大,向上进行HeapInsert操作
         若是变小,向下进行Heapify操作
堆排序:(将HeapInsert与Heapify结合操作)
时间复杂度O(N*logN),空间复杂度O(1)
思路:
(1)先将数组排成大根堆,纪录此时heapSize=数组长度
将数组排成大根堆的方法除了用HeapInsert,还可以利用Heapify来进行

        for (int i = arr.Length - 1; i >= 0; i--)
        {
            Heapify(arr,i,arr.Length);
        }

该方法是先从最后一个节点看,从下往上进行大根堆的制作。(确保每个子树是大根堆)
注:
利用该方法会比使用HeapInsert快一点点,但堆排序时间复杂度还是O(N*logN)
(2)将大根堆头节点与最后一个数交换(此时最后一个数位置排好),heapSize–;
(3)对除最后一个数进行Heapify,变成大根堆重复(2)(3)操作,知道heapSzie=0;

代码实现:

public void HeapSorts(int[] arr)
        {
            if (arr == null || arr.Length < 2)
                return;
            /*for (int i = 0; i < arr.Length; i++)//O(N)
            {
                HeapInsert(arr,i);//O(logN)
            }*/
            //还可以使用heapify来进行大根堆制作
            for (int i = arr.Length - 1; i >= 0; i--)//O(N)
            {
                Heapify(arr,i,arr.Length);//O(logN)
            }

            int heapSize = arr.Length;
            Swap(arr,0,--heapSize);
            while (heapSize > 0)
            {
                Heapify(arr, 0,heapSize);
                Swap(arr,0,--heapSize);
            }
        }

        //先把数组搞成大根堆
        public void HeapInsert(int[] arr,int index)
        {
            while (arr[index] > arr[(index - 1) / 2])
            {
                Swap(arr,index,(index-1)/2);
                index = (index - 1) / 2;
            }
        }

        public void Heapify(int[] arr, int index,int heapSize)
        {
            int left = 2 * index + 1;
            while (heapSize > left) //还有孩子时
            {
                int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
                largest = arr[largest] > arr[index] ? largest : index;
                if (largest == index)
                    break;
                Swap(arr,largest,index);
                index = largest;
                left = 2 * index + 1;
            }
        }

        public void Swap(int[] arr, int i, int j)
        {
            int index = arr[i];
            arr[i] = arr[j];
            arr[j]=index;
        }
    }

堆排序例题:
已知一个几乎有序的数组。几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过K,并且K相对于数组来说比较小。请选择合适排序算法对这个数据进行排序。

解题思路:使用堆排序
设K=4,数组arr={0,3 ,5, 7,1,2,4,5,7}
(1)准备一个小根堆,遍历数组,先将数组位置为0~4的5个数放到小根堆,排好。然后将小根堆的头节点放到数组的0位置。
(解释:因为由于每个数移动位置不超过K,所以对于数组中排序后位于第一个位置的数,它的活动返为应该就在0到4位置间,所以在未排序的数组中0~4位置一定有数组最小值[即位置在第0位置的数])
(2)将第5位放入小根堆,排好,将头节点放到数组的1位置。
(3)将第6位放入小根堆,排好,将头节点放到数组的2位置

直到将最后一个数纳入小根堆,然后依次把小根堆中最小值弹出,放到数组中即可。
在这里插入图片描述
代码实现:

public void Extern(int[] arr,int k)
    {
        int[] help = new int[k+1];
        int index = 0;
        int end = k;
        //让help变成小根堆
        for (int i = 0; i < k+1; i++)
        {
            help[i] = arr[i];
            SmallHeapInsert(help,i);
        }
        while (end != arr.Length-1)
        {
            arr[index++] = help[0];
            help[0] = arr[++end];//help后移,加入新数到头结点覆盖掉之前的最小值
            SmallHeapify(help,0);
        }
        //将最后一个数纳入help中,编程小根堆后,对help中的位置1~k数字排序,然后一次放到arr空余的位置上
        BubbleSort(help,1,k);
        for (int w = 0; w < k + 1; w++)
        {
            arr[index++] = help[w];
        }
        Console.WriteLine(string.Join(",",arr));
    }

    public void SmallHeapInsert(int[] arr, int heapSize)
    {
        while (arr[heapSize] < arr[(heapSize - 1) / 2])
        {
            Swap(arr,heapSize,(heapSize - 1) / 2);
            heapSize = (heapSize - 1) / 2;
        }
    }

    public void SmallHeapify(int[] help, int i)
    {
        int left = 2 * i + 1;
        while (left < help.Length)
        {
            int smallest = left + 1 < help.Length && help[left] < help[left + 1] ? left : left + 1;
            smallest = help[smallest] < help[i] ? smallest : i;
            if (smallest == i)
            {
                break;
            }
            Swap(help,i,smallest);
            i = smallest;
            left = 2 * i + 1;
        }
    }

    public void BubbleSort(int[] help,int start,int end)
    {
        int length = end - start + 1;
        for (int i = 0; i < length; i++)
        {
            for (int j = start; j < end; j++)
            {
                if(help[j]>help[j+1])
                    Swap(help,j,j+1);
            }
        }
    }

    public void Swap(int[] arr, int i, int j)
    {
        int index = arr[i];
        arr[i] = arr[j];
        arr[j] = index;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值