C#写算法之快速排序

快速排序是从冒泡排序演变而来,快速排序之所以快,是因为使用了分治法

同冒泡排序一样,快速排序也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的。

快速排序是在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列的一边,比它晓得元素移动到数列的另一边,从而把数列拆解成两个部分。

在分治法的思想下,原数列在每一轮都被拆分成两部分,每一部分在下一轮又分别被拆分成两部分,直到不可再分为止。

假如元素个数是n,那么平均情况下需要logn轮,因此快速排序的平均算法复杂度是O(nlogn),最坏情况是O(n^2)。

元素的交换

1.选基准元素;2.数组分为大于和小于基准的两份。

实现方法:(1).双边循环法;(2).单边循环法。

首先选定基准元素pivot,并且设置两个指针left和right,指向数列的最左和最右两个元素。

第1次循环从right指针开始,让指针指向元素和基准元素做比较。如果大于或等于pivot,则指针向左移动;如果小于pivot,则right指针停止移动,切换到left指针。

轮到left指针行动,让指针所指向的元素和基准元素做比较,如果小于或等于pivot,则指针向右移动;如果大于pivot,则left指针停止移动。

1.双边循环法

using System;

namespace CSharpTest01
{
    class Program
    {
        public static void QuickSort(int[] arr, int startIndex, int endIndex)
        {
            // 递归结束条件:startIndex大于或等于endIndex时
            if (startIndex >= endIndex)
            {
                return;
            }
            // 得到基准元素位置
            int pivotIndex = 
        }
        /// <summary>
        /// 分治(双边循环法)
        /// </summary>
        private static int partition(int[] arr, int startIndex, int endIndex)
        {
            // 取第1个位置(也可以选择随机位置)的元素作为基准元素
            int pivot = arr[startIndex];
            int left = startIndex;
            int right = endIndex;
            while (left != right)
            {
                // 控制right指针比较并左移
                while (left < right && arr[right] > pivot)
                {
                    right--;
                }
                // 控制left指针比较并右移
                while (left < right && arr[left] <= pivot)
                {
                    left++;
                }
                // 交换left和right指针所指向的元素
                if (left < right)
                {
                    int p = arr[left];
                    arr[left] = arr[right];
                    arr[right] = p;
                }
            }
        }

        static void Main(string[] args)
        {
            
            Console.ReadLine();
        }
    }
}

上述代码中,QuickSort通过递归的方式,实现了分而治之的思想。

Partition实现元素交换,让元素依据自身大小,分别交换到基准的左右。这里的交换方式是双边循环法。

2.单边循环法

开始和双边循环法相似,首先选定基准元素pivot。同时,设置一个mark指针指向数列起始位置,这个mark指针代表小于基准元素的区域边界

接下来,从基准元素的下一个位置开始遍历数组。如果遍历到的元素大于基准元素,就继续往后遍历 。

如果遍历到的元素小于基准元素,则需要做两件事:第一,把mark指针右移1位,因为小于pivot的区域边界增大了1;第二,让最新遍历到的元素和mark指针所在的元素交换位置,因为最新遍历的元素属于小于pivot的区域。

随后,让3和mark指针所在位置的元素交换。

按照这个思路,继续遍历。

using System;
using System.Collections;

namespace CSharpTest01
{
    class Program
    {
        public static void QuickSort(int[] arr, int startIndex, int endIndex)
        {
            // 递归结束条件:startIndex大于或等于endIndex时
            if (startIndex >= endIndex)
            {
                return;
            }
            // 得到基准元素
            int pivotIndex = Partition(arr, startIndex, endIndex);
            // 根据基准元素,分成两部分进行递归排序
            QuickSort(arr, startIndex, pivotIndex - 1);
            QuickSort(arr, pivotIndex + 1, endIndex);
        }
        /// <summary>
        /// 分治(单边循环法)
        /// </summary>
        private static int Partition(int[] arr, int startIndex, int endIndex)
        {
            // 取第1个位置(也可以是随机位置)的元素作为基准元素
            int pivot = arr[startIndex];
            int mark = startIndex;
            for (int i = startIndex+1; i <= endIndex; i++)
            {
                if (arr[i] < pivot)
                {
                    mark++;
                    int p = arr[mark];
                    arr[mark] = arr[i];
                    arr[i] = p;
                }
            }
            arr[startIndex] = arr[mark];
            arr[mark] = pivot;
            return mark;
        }

        static void Main(string[] args)
        {
            int[] arr = new int[] { 4, 4, 6, 5, 3, 2, 8, 1 };
            QuickSort(arr, 0, arr.Length - 1);
            foreach (var item in arr)
            {
                Console.WriteLine(item);
            }
        }
    }
}

3.非递归实现:

把原本的递归实现转化成一个栈的实现,在栈中存储每一次方法调用的参数。

using System;
using System.Collections;
using System.Collections.Generic;

namespace CSharpTest01
{
    class Program
    {
        public static void QuickSort(int[] arr, int startIndex, int endIndex)
        {
            // 用一个集合栈来代替递归的函数栈
            Stack<Dictionary<string, int>> quickSortStack = new Stack<Dictionary<string, int>>();
            // 整个数列的起止下标,以哈希的形式入栈。
            Dictionary<string, int> rootParam = new Dictionary<string, int>();            
            rootParam.Add("startIndex", startIndex);
            rootParam.Add("endIndex", endIndex);
            quickSortStack.Push(rootParam);

            // 循环结束条件:栈为空时
            while (quickSortStack.Count > 0)
            {
                // 栈顶元素出栈,得到起止下标
                Dictionary<string, int> param = quickSortStack.Pop();
                // 得到基准元素位置
                int start = 0;
                param.TryGetValue("startIndex", out start);
                int end = 0;
                param.TryGetValue("endIndex", out end);
                int pivotIndex = Partition(arr, start, end);
                // 根据基准元素分成两部分,把每一部分的起止下标入栈
                if (start < pivotIndex - 1)
                {
                    Dictionary<string, int> leftParam = new Dictionary<string, int>();
                    leftParam.Add("startIndex", start);
                    leftParam.Add("endIndex", pivotIndex-1);
                    quickSortStack.Push(leftParam);
                }
                if (pivotIndex + 1 < end)
                {
                    Dictionary<string, int> rightParam = new Dictionary<string, int>();
                    rightParam.Add("startIndex", pivotIndex + 1);
                    rightParam.Add("endIndex", end);
                    quickSortStack.Push(rightParam);
                }
            }
        }
        /// <summary>
        /// 分治(单边循环法)
        /// </summary>
        private static int Partition(int[] arr, int startIndex, int endIndex)
        {
            // 取第1个位置(也可以是随机位置)的元素作为基准元素
            int pivot = arr[startIndex];
            int mark = startIndex;
            for (int i = startIndex+1; i <= endIndex; i++)
            {
                if (arr[i] < pivot)
                {
                    mark++;
                    int p = arr[mark];
                    arr[mark] = arr[i];
                    arr[i] = p;
                }
            }
            arr[startIndex] = arr[mark];
            arr[mark] = pivot;
            return mark;
        }

        static void Main(string[] args)
        {
            int[] arr = new int[] { 4, 4, 6, 5, 3, 2, 8, 1 };
            QuickSort(arr, 0, arr.Length - 1);
            foreach (var item in arr)
            {
                Console.WriteLine(item);
            }
        }
    }
}

每一次循环,都会让栈顶元素出栈,通过partition方法进行分治,并且按照基准元素的位置分成左右两部分,左右两部分再分别入栈。当栈为空时,说明排序已经结束,退出循环。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Kerven_HKW

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

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

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

打赏作者

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

抵扣说明:

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

余额充值