• 这可能是我见过最详细的快速排序!


    关于快速排序,网上,和维基都有完成的解释,他们都是。。。。。。,俺觉得都是,太过于总结话语在概述一些东西;

    而我却从最本质的东西,一步一步的深入;在深入的学习过程中,我得到如下非代码层面上的感悟;

    A.一个完整的模式(代码或者其他东西)都是通过没有=>part0=>part1=>part2>version0=>viersion1.......versionN=>perfect  最后达到一个完成的形式;

    如果你只是去学习和体会最后的那一个完成的版本,而没有经历整个探索的过程,你的体会也不会太多;

    B.有时候要学会抽象化和跳跃化的去思考问题,而不是step by step(这有点不具体,下次我结合具体的实例来解释)

    C.帮助别人解决问题;并不是直接给出问题的答案或者结果;而是给别人一些引导和资源,让他自己去解决;(这点来自使用stackoverflow提问的感受)

    D.还有第四点,要学会从特殊(具体)到一般(抽象)的探索过程;比如,我们可以先总结出满足大于零的规律,然后是小于零的规律,然后是等于零的规律,这些都是特殊(或者你可以理解成具体);从这些特殊规律总结出一般性(通用性)的规律;

    E.还有当研究出了算法之后,要学会如何去验证自己的一般性规律,在我们的code,就是要学会写测试用例,进行验证;

    好了,废话不多了;直接从代码开始;开始之前,想尝试做下面的practice;

    1.选择数组的第一个data(target)将数组分成两部分;满足:   minArr<target<=maxArr;

    2.选择数组的第一个data(target)将数组分成两部分;满足:   minArr<target<=maxArr; 并将target 放入数组“中间位置“;满足:  target greater than left data and less than right data;

    3.找出value 在有序数组中的位置;

    4.找出value 在无序数组中的位置;这个位置应该满足,它在有序数组中应该具有的位置;greater than left data and less than right data;

    5.在不开辟内存的情况下(使用新的数组来存储值)完成问题2;

    6.关于递归的学习和使用(具体看这篇文章的后半部分:http://www.cnblogs.com/mc67/p/5114008.html)

    7.三种方式来实现我们的额快速排序;(本质的区别只有两种)

    8.通过Unit Test 来验证我们的代码;

    关于前面的 5个问题,我就直接上代码;你细细品味(建议在没有看代码前,完成上面的问题)

     /// <summary>
            /// 最基本的;将数组分成两部分;一部分比target小,一部分比target大;
            /// </summary>
            /// <param name="arr"></param>
            static void SplitArray(int[] arr)
            {
                int len = arr.Length;
                int target = arr[0];
                List<int> min = new List<int>(len); //为了方便,这里我使用List
                List<int> max = new List<int>(len); //为了方便,这里我使用List
                for (int i = 1; i < len; i++) //从第二个元素开始查找;
                {
                    if (arr[i] < target)
                    {
                        min.Add(arr[i]);
                    }
                    else
                    {
                        max.Add(arr[i]);  // as euqual condition,I put it into max;
                    }
                }
    
            }
            /// <summary>
            /// split the arr and put the target in the right position(greater than left ,less than right)
            /// </summary>
            /// <param name="arr"></param>
            static void SplitArray1(int[] arr)
            {
                int len = arr.Length;
                int target = arr[0];
                List<int> min = new List<int>(len); //为了方便,这里我使用List
                List<int> max = new List<int>(len); //为了方便,这里我使用List
                for (int i = 1; i < len; i++) //从第二个元素开始查找;
                {
                    if (arr[i] < target)
                    {
                        min.Add(arr[i]);
                    }
                }
                //put it in the last of min, that can make sure target greater than min(left)
                min.Add(target);
                for (int i = 1; i < len; i++) //从第二个元素开始查找;
                {
                    if (arr[i] > target)
                    {
                        max.Add(arr[i]);
                    }
                }
    
                min.AddRange(max); //that can make sure  min<target<max
            }
    
            /// <summary>
            /// optimze the SplitArray1
            /// 上面的代码,还是有问题的,如果有重复的值,那么,将在判断max的时候丢掉;
            /// 那么问题来了,如果相等的如何判断呢;
            /// </summary>
            /// <param name="arr"></param>
            static void SplitArray2(int[] arr)
            {
                int len = arr.Length;
                int target = arr[0];
                List<int> min = new List<int>(len); //为了方便,这里我使用List
                List<int> max = new List<int>(len); //为了方便,这里我使用List
                for (int i = 1; i < len; i++) //从第二个元素开始查找;
                {
                    if (arr[i] < target)
                    {
                        min.Add(arr[i]);
                    }
                }
                //put it in the last of min, that can make sure target greater than min(left)
                min.Add(target);
                for (int i = 1; i < len; i++) //从第二个元素开始查找;
                {
                    if (arr[i] >= target) //我们把等于符号加上,就解决问了???? 对于本例子,是从arr[0] 开始的;//那如果是从别的位置开始呢;
                    {
                        max.Add(arr[i]);
                    }
                }
    
            }
    
    
            /// <summary>
            /// start from random index;
            /// </summary>
            /// <param name="arr"></param>
            static void SplitArray3(int[] arr)
            {
                int len = arr.Length;
                int randomIndex = 2;
                int target = arr[randomIndex]; //假设,我们从index=2 开始,这里,我们肯定满足arr.length>=2;
    
                List<int> min = new List<int>(len); //为了方便,这里我使用List
                List<int> max = new List<int>(len); //为了方便,这里我使用List
                for (int i = 0; i < len; i++) //这里,我们就要从0 开始了,遍历整个数组;
                {
                    if (arr[i] < target)
                    {
                        min.Add(arr[i]);
                    }
                }
                min.Add(target);
                for (int i = 0; i < len; i++) //这里,我们就要从0 开始了,遍历整个数组;
                {
                    if (arr[i] >= target && i != randomIndex) //加上这个条件,我们就可以过滤到我们的 randomIndex 避免重复添加的问题; 
                    {
                        max.Add(arr[i]);
                    }
                }
    
                //最后,我们的数组,就是符合我们要求的数组;
            }
    
    
            //上面的做法;可以满足  min<target<=max
            /// <summary>
            /// 我们来找出元素所在的位置 index 首先是我们的有序数组中
            /// 通过值相等来进行判断的话,可以满足;
            /// 不管值有序 还是 无序的值;
            /// </summary>
            static void FindIndex(int[] arr, int value)
            {
    
                int len = arr.Length;
                int index = -1;
                for (int i = 0; i < len; i++)
                {
                    if (arr[i] == value)
                    {
                        index = i;
                        break;
                    }
                }
            }
    
            /// <summary>
            /// 两种思维方式,
            /// 两种思维方式;第二种,更接近普通人的表达方式;
            /// </summary>
            /// <param name="arr"></param>
            /// <param name="value"></param>
            static void FindIndex1(int[] arr, int value)
            {
    
                int len = arr.Length;
                int index;
                bool isExist = false;
                for (int i = 0; i < len; i++)
                {
                    if (arr[i] == value)
                    {
                        index = i;
                        isExist = true;
                        break;
                    }
                }
                if (isExist == false) { index = 1; }
            }
    
            /// <summary>
            /// 上面的方法是找到,value,在数组中的第一个index 值; 不管它是否有序;
            /// 现在我们要找一个元素,在有序列表中的(应该插入的值,但是我们不插入)
            /// </summary>
            static void FindIndex2(int[] arr, int value)
            {
    
                int len = arr.Length;
                int index = -1;
                for (int i = 0; i < len; i++)
                {
                    //你可以这么想;
                    //如果找到大于大的数就停止;否则就继续
                    if (arr[i] >= value)  //考虑到取等情况;
                    {
                        index = i; //这个时候,我们的数据,就可以插入在改元素的的后面;
                        index = index - 1;  //这样就返回了可以直接插入的位置;
                        //并且停止我们的循环;
                        break;    //前提是有序的数组列表中
                    }
                }
    
                //在插入的时候,就必须把后面的元素往后面挪动;
                //插入的时候,
            }
    
            /// <summary>
            /// 上面的方法是找到,value,在数组中的第一个index 值; 不管它是否有序;
            /// 现在我们要找一个元素,在有序列表中的(应该插入的值,但是我们不插入)
            ///  2 和 3 两种不同的想法,写出来的code 就不太一样;
            /// </summary>
            static void FindIndex3(int[] arr, int value)
            {
    
                int len = arr.Length;
                int index = -1;
                for (int i = 0; i < len; i++)
                {
                    //我们可以可以这么想; 
                    if (arr[i] <= value)
                    {
                        index++;  //小于它的值,我们的index 就 keep move forward;  还没考虑,我们取等的情况滴呀;
                    }
                    else
                    {
                        //遇到,不满足的情况,我们就退出
                        //原本的我们的index 是落后于我们的i,出去的时候;再加一次,
                        index++;
                        break;
                    }
                }
    
                //这样就找到了我们的index;在一个可以插入的位置;
            }
    
    
            /// <summary>
            /// 先这样来想,找出小于 value的count,那么value在的位置应该就是在我们的count+1;
            /// </summary>
            /// <param name="arr"></param>
            /// <param name="value"></param>
            static void FindCountLessThan(int[] arr, int value)
            {
                int len = arr.Length;
                int count = 0;
                for (int i = 0; i < len; i++)
                {
                    if (arr[i] <= value)
                    {
                        count++;
                    }
                }
    
                //这样,我们就能够找出小于count的数量;那么 我们value所在的位置就是我们count+1
            }
    
            /// <summary>
            /// 关键的来了:找到value 应该在的位置;在一个无序的数组中;
            /// </summary>
            /// <param name="arr"></param>
            /// <param name="value"></param>
            static void FindIndexInUnSortArr(int[] arr, int value)
            {
                int len = arr.Length;
                int rightPosition = 0;  //初始化,默认我们的指针在0位置;
                for (int i = 0; i < len; i++)
                {
                    //通过遍历来查找;
                    if (arr[i] <= value)
                    {
                        rightPosition++;
                    }
                }
            }
    
            /// <summary>
            /// 找到index在的位置,并将小于value的数据放在左边,大于value的数据放在右边
            /// </summary>
            /// <param name="arr"></param>
            /// <param name="value"></param>
            static void FindeAndSwap(int[] arr)
            {
    
                int len = arr.Length;
                int middleIndex = 1;
                int value = arr[0];//target;
    
                // i 能找到一个小于value的值;
                //middleIndex 始终指向一个大于或等于 value的值;
                for (int i = 1; i < len; i++)
                {
                    if (arr[i] <= value) //当找到一个小于value的值之后,进行交换,和那个值进行交换呢;(原则:小的值移动到左边,大的值移动到右边;)
                    {                    //现在,我们找到了小的值,那么大的值呢;????
                        if (i == middleIndex)
                        {
                            //不进行交换; keep move
                        }
                        else
                        {
                            Swap(arr, middleIndex, i);
                        }
                        middleIndex++;
                    }
                }
    
                //最后出来后,我们要将第一个元素和中间的元素进行交换,也就是讲value放在middle的位置;
                Swap(arr, 0, middleIndex - 1);
            }
    
            /// <summary>
            ///  上面的代码,基本上已经满足了我们额基本要求;
            ///  完美的代码,解决了这个问题;
            /// </summary>
            /// <param name="arr"></param>
            static int FindeAndSwap1(int[] arr, int start, int end)
            {
                int middlePosition = start + 1;
                int pivot = arr[start];
    
                for (int i = start + 1; i <= end; i++)
                {
    
                    if (arr[i] <= pivot)
                    {
                        if (i == middlePosition)
                        {
                            //there is need to swap 
                        }
                        else
                        {
                            Swap(arr, middlePosition, i);
                        }
                        middlePosition++;
                    }
    
                }
                //put the arr[start] in "middle position"(greater than left,less than right)
                int position = middlePosition - 1;
                Swap(arr, arr[start], position);
                return position;
            }
    
            /// <summary>
            /// 同样,我们又第二种方式来实现,
            /// </summary>
            /// <param name="arr"></param>
            /// <param name="low"></param>
            /// <param name="high"></param>
            static void FindeAndSwap2(int[] arr, int low,int high)
            {
                
                int l = low-1;  //make sure pointer move firstly (before take value from arr to compare with pviot)
                int h = high+1; //make sure pointer move firstly (before take value from arr to compare with pviot)
                int pviot = arr[low]; 
                while (l<h)
                {
                    while (arr[--h]> pviot) //find some value less than pvoit
                    {
    
                    }
                    while (arr[++l] <= pviot) //find some value greater than pvoit   we use <= instead of <;beacause we don't let arr[start] swap 
                    {
    
                    }
                    if (l < h)
                    {
                      Swap(arr, h, l);  //swap
                    }
                }            
                Swap(arr, low, h);    //put the povit in the "middle" Position
    
            }
    
            /// <summary>
            /// 同样,我们也有第三种写法;
            /// </summary>
            /// <param name="arr"></param>
            /// <param name="low"></param>
            /// <param name="high"></param>
            static int FindeAndSwap3(int[] arr, int low, int high)
            {
                int l = low;
                int h = high;
                int pviot = arr[low];
    
                while (l < h)
                {
                    while (arr[h] >= pviot) //chose >= instead of >; beca if the first value arr[h] equal  pviot ,this will enter endless loop;(don't enter {} do h--;)
                    {
                        h--;
                    }
                    while (arr[l] <= pviot)  //we chose <= instead of < to make sure pviot don't take part in swap, we will swap in the last step with "middle" position
                    {
                        l++;
                    }
                    if (l >= h)
                        break;
                    Swap(arr,l,h);
                }
                int middlePosition = h;
                Swap(arr,low, middlePosition);
                return middlePosition;
            }
    
            /// <summary>
            /// 当然,就有了,我们的第四种方法;
            /// 你会发现,前面的方法都是,先找到一个小于的index  high 然后找到一个大于的index low
            /// 然后进行交换;
            /// 然后有没有其他的方式呢? 
            /// 答案是有的;
            /// 而且,你会发现,我们的额pvoit 是没有参与swap的,直到我们的最后一步,然后将起放在 middle position(这一步,是不可避免滴呀)
            /// </summary>
            /// <param name="arr"></param>
            /// <param name="low"></param>
            /// <param name="high"></param>
            /// <returns></returns>
            static int FindeAndSwap4(int[] arr, int low, int high)
            {
                int pviot = arr[low];
    
                while (low < high)
                {
                    while(arr[high]>=pviot && low < high)
                    {
                        high--;
                    }
                    //一旦,找到了,我们就替换;
                    arr[low] = arr[high];  //这样,会覆盖我们的第一个值,不过,在最后,我们会将第一个值,放在“中间”位置;
                    while (arr[low] <= pviot && low < high)
                    {
                        low++;
                    } //这样做的话,在没有,进行到最后一步,数组中会有一个重复的值,不过,我们最后将被我们pviot
                    arr[high] = arr[low];
                }
                arr[low] = pviot;
                return high;
            }
    
            //到了这一步,我们的基本核心的单元,算是基本基本完整了;
            //然后,我们这里,再实现,三个版本的快速排序;方法;
    
    
            /// <summary>
            /// 交换
            /// </summary>
            /// <param name="arr"></param>
            /// <param name="i"></param>
            /// <param name="j"></param>
            static void Swap(int[] arr, int i, int j)
            {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }

     1.然后是快速排序的第一种实现方式:(从两端申明指针然后,开始查找; 这个算法是有问题的:to do.....做单元测试来一步一步的发现问题)

            /// <summary>
            /// 第一种实现方式,从两边开始查找;
            /// 还有一个值得考虑问题就是,等于的数据,如何处理呢;从我们的代码看出,等于pviot的数据,没有参与到交换中;
            /// 第一次交换之后将流在我们的 两端;参与下一次的交换;
            /// </summary>
            /// <param name="arr"></param>
            /// <param name="low"></param>
            /// <param name="high"></param>
            /// <returns></returns>
            static int QuickSortUnit(int[] arr, int low, int high)
            {
    int pviot=arr[low]; //we chose the first value as pivot;
    while (low < high) { while (arr[high] >= pviot && low<high) //>= instead of > because: if the arr[high] less than pviot,that will result in endless loop(high index never do high--;) { high--; //start search from high address } while (arr[low] <= pviot && low<high) // <= instead of < becuase:we can make sure arr[first] did't involve the whole swap until the ending { low++; //start search from low address } //we both chose equal,after first loop,the equivalent will remianing the origial positon and take part in next sort; that does't affect the final result; if (low >= high) break; Swap(arr, low, high); } //because we firstly star from high; so the last value that must less than pviot;so high is our "middle" position; int middlePosition = high; //make the code easy to read; Swap(arr,middlePosition,low); return high; } /// <summary> /// recurion to resolve the same problem; /// </summary> /// <param name="arr"></param> /// <param name="low"></param> /// <param name="high"></param> static void QuickSort(int [] arr,int low,int high) { if (low >= high) //make sure recusion can stop return; int middlePositon=QuickSortUnit(arr,low,high); //star from left; QuickSort(arr,low, middlePositon-1); //star from right QuickSort(arr, middlePositon+1,high); }

    2.然后是快速排序的第二种实现方式(本质上和第一种方式一样的,只不过写法(想法),略有不同,一旦找到元素就开始覆盖指定位置的值,这样每次都会有一个重复的值,不过这个重复的值,最后会被我们的pivot给占据)

            /// <summary>
            /// 
            /// </summary>
            /// <param name="arr"></param>
            /// <param name="low"></param>
            /// <param name="high"></param>
            static int QuickSortUnit(int[] arr, int low, int high)
            {
                int pviot = arr[low];
    
                while (low < high)
                {
                    while (arr[high] >= pviot && low<high)
                    {
                        high--;
                    }
                    arr[low] = arr[high]; //once find the value less than pviot ,put it in low address;
                    while (arr[low] <= pviot && low < high)
                    {
                        low++;
                    }
                    arr[high] = arr[low]; //once find the value greater than pviot, put it in high address;
                }
    
                int middlePositon = high;
                arr[low] = pviot; 
                return middlePositon;
    
            }
    
            /// <summary>
            /// use recursion to resovle the same problem;
            /// </summary>
            /// <param name="arr"></param>
            /// <param name="low"></param>
            /// <param name="high"></param>
            static void QuickSort(int [] arr,int low,int high)
            {
    
                if (low >= high)
                    return;
                int middlePosition = QuickSortUnit(arr,low,high);
    
                 QuickSortUnit(arr, low, middlePosition-1);
    
                 QuickSortUnit(arr, middlePosition+1, high);
    
            }

    思考:

       最后一步交互为哈一定要取low呢;(debug 一下这个:    var arr = new[] { 7, 8, 7 };)

    第三种方式;其实,本质是一样,不过,这种方式,不用从两端申明pointer,去查找(伴随着两个while循环);这里我们用一个循环,还是两个pointer,不过他们的出发位置,就有所不同了,都从左边开始;

     提现了不同的思路去解决问题;

     最后一种,我们就直接给链接吧;

     https://www.hackerearth.com/zh/practice/algorithms/sorting/quick-sort/tutorial/

    非常好的网站;

    这里,我们再来总结一下,整个执行过程:

    1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;
     
    2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];
     
    3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]互换;
     
    4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
     
    5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-
     

    最后,就是要通过写单元测试,来assert我们的值;

  • 相关阅读:
    深度学习调参笔记(trick)
    Linux调用Kaggle API下载数据
    Jupyter Notebook 入门指南
    ondyari / FaceForensics配置指南
    python读写文件
    Federated Learning with Matched Averaging
    Advances and Open Problems in Federated Learning
    使用标准输入对话框
    各类位置信息
    标准对话框的使用
  • 原文地址:https://www.cnblogs.com/mc67/p/8259734.html
Copyright © 2020-2023  润新知