快速排序和归并排序的非递归C++实现

1.快速排序

快速排序的基本思想是,每次选取一个数组元素中的基准值(通常为首元素)作为参考,将这个数组分为左右两部分,左边的部分全部小于这个基准值的右边的部分全部大于这个基准值。然后在左右子序列重复这个步骤最后就能得到有序序列。从这个算法描述来看,很显然这是一种不稳定的排序算法。并且如果选择的基准值不合适就可能会导致数据全部在左边或者右边,这时候会使得快排的效率极大降低。

因此我们可以通过以下常用的两种方式来解决这个问题:第一种方式就是每次选基准值的时候都是从数组中选取一个随机的元素放到第一个位置来做基准值。但是这种方式仍然可能会出现上述的情况。第二种方式就是我们每次可以选择在数组的首尾以及中间三个元素中取中间的值放到首元素位置上,这种方式比选取随机值的方式更有效。

下面先给出三数取中的方式

    /*
    int GetMid(vector<int>& a,int left,int right)
    {
        int mid = (left + right) / 2;
        if (a[left] < a[mid])
	    {
		    if (a[mid] < a[right])
		    {
			    return mid;
		    }
		    else if(a[right] < a[left])
		    {
			    return left;
		    }
		    else
		    {
			    return right;
		    }
	    }
	    else//a[left]>a[mid]
	    {
		    if (a[mid] > a[right])
		    {
			    return mid;
		    }
		    else if (a[right] > a[left])
		    {
			    return left;
		    }
		    else
		    {
			    return right;
		    }
	    }
    }

显而易见的是,这种思想很容易使用递归实现,下面给出一个霍尔版本的递归快排。

    /*void QuickSort(vector<int>& a,int left,int right)
    {
        if(left >= right)
            return;

        int begin = left;
        int end = right;

        int midi = GetMid(a,left,right);
        swap(a[left],a[midi]);

        int keyi = left;
        while(begin < end)
        {
            //先走右边,找小于key的
            while(begin < end && a[end] >= a[keyi])
            {
                --end;
            }

            //后走左边,找大于key的
            while(begin < end && a[begin] <= a[keyi])
            {
                ++begin;
            }

            swap(a[begin],a[end]);
        }
        swap(a[keyi],a[begin]);

        QuickSort(a,left,begin - 1);
        QuickSort(a,begin + 1,right);
    }

接下来我们来实现它的非递归实现,在实现之前,有3个问题需要解决。

第一个问题是:我们先来分析快排到底是怎么控制元素的?通过上面的代码可以看到,实际上快排是每次通过控制它的区间来控制递归排序的。从最后两行的递归调用代码就可以看到这一点。

第二个问题是:我们非递归实现的思路是什么呢?最容易想到的就是我们也去模拟它的递归的过程,将每次排序单独写一个函数放在外面,每次控制好区间之后调用这个函数并且顺带返回它选取的基准值,这里的基准值还是使用三数取中的方式,这样就能达到模拟递归实现的效果。

第三个问题是:我们如何将它每次排序完的区间保存下来并且更新呢?这里我们可以利用栈的特性实现这个效果,这里的思路就是我们可以在每次排序之前先把之前排好序的区间出栈,把下一次将排序的区间入栈,每次top取栈顶元素得到待排序的区间。只要栈不为空就重复上面的过程。

下面给出具体的代码:

int _QuickSort(vector<int>& a,int left,int right)
    {
        int begin = left;
        int end = right;

        int midi = GetMid(a,left,right);
        swap(a[left],a[midi]);
        int keyi = left;
        while(begin < end)
        {
            //先走右边
            while(begin < end && a[keyi] <= a[end])
            {
                --end;
            }
            //后走左边
            while(begin < end && a[keyi] >= a[begin])
            {
                ++begin;
            }
            swap(a[end],a[begin]);
        }
        swap(a[begin],a[keyi]);
        return begin;
    }

    void QuickSort(vector<int>& a,int left,int right)
    {
        if(left >= right)
            return;

        stack<pair<int,int>> st;
        st.push({left,right});
        while(!st.empty())
        {
            pair<int,int> interval = st.top();
            st.pop();

            int keyi = _QuickSort(a,interval.first,interval.second);//得到分区的那个元素下标
            
            // 检查区间的合法性
            if(interval.first < keyi - 1)
                st.push({interval.first,keyi-1});
            if(interval.second > keyi + 1)
                st.push({keyi+1,interval.second});
        }
    }
    */

2.归并排序

归并排序的基本思想是:将带排序的序列分为子序列,使得每一个子序列有序之后再合并这些子序列就能得到有序的序列。这是分治法的经典应用

同样从算法描述来看使用递归很容易实现归并排序,我们只需要关心分组后的子序列的问题即可。

下面给出归并排序的递归实现:

  void mergSort(vector<int>& a,int left,int right,vector<int>& _temp)
    {
        if(left >= right)
            return;
        
        int mid = (left + right)/2;
        mergSort(a,left,mid,_temp);
        mergSort(a,mid+1,right,_temp);

        //只关心分组后的子问题
        int begin1 = left,end1 = mid;
        int begin2 = mid+1,end2 = right;
        int i = left;
        while(begin1 <= end1 && begin2 <= end2)
        {
            if(a[begin1] < a[begin2])//第一组的当前元素小
            {
                _temp[i++] = a[begin1++];
            }
            else
            {
                _temp[i++] = a[begin2++];
            }
        } 

        //要么是第一组没完,要么是第二组没完
        while(begin1 <= end1)
        {
            _temp[i++] = a[begin1++];
        }

        while(begin2 <= end2)
        {   
            _temp[i++] = a[begin2++];
        }
        
        for (i = left; i <= right; ++i)
        {
            a[i] = _temp[i];
        }
    }

接下来我们来实现归并排序的非递归实现。首先对于归并的非递归我们需要将数组分为若干个子数组,然后将子数组两两合并直到整个数组有序。这里我们可以控制一个步长来对于数组进行分组,每次归并排完子数组之后更新gap进入下一次分组。但是在这里需要注意分组之后的下标越界问题,每次分分组之后都要对于下标进行检查更新。具体的检查逻辑在我在代码中给出了详细的注释,读者根据代码理解可能更好理解

    void mergSort(vector<int>& a,int n,vector<int>& temp)
    {
        //归并排序的非递归事实上也是模拟递归的分组实现的过程
        int gap = 1;//第一次排序时每组只有一个元素进行排序,这里代表的是分组后子数组的大小
        while(gap < n)
        {
            for(int i = 0;i < n; i += gap * 2)
            {
                int begin1 = i,end1 = i + gap -1;
                int begin2 = i + gap,end2 = i + 2 * gap -1;
                int j = i;//这里设置一个中间数组的小标能够存入排序的数组
                //注意检查越界
                //1.begin1不会越界
                //2.end1,begin2,end2会越界
                //当end1和begin2越界时,都可以说明第二个子数组不存在也就不需要归并
                //当end2越界时,这时候把下标调整到整个数组的最后一个元素即可
                if(end1 >= n || begin2 >= n)
                {
                    break;
                }
                if(end2 >= n)
                {
                    end2 = n - 1;
                }

                while(begin1 <= end1 && begin2 <= end2)
                {
                    if(a[begin1] < a[begin2])
                    {
                        temp[j++] = a[begin1++];
                    }
                    else
                    {
                        temp[j++] = a[begin2++];
                    }
                }

                while(begin1 <= end1)
                {
                    temp[j++] = a[begin1++];
                }

                while(begin2 <= end2)
                {
                    temp[j++] = a[begin2++];
                }

                for (int k = i; k <= end2; k++)
                {
                    a[k] = temp[k];
                }
            }
            gap *= 2;
        }
    }

$(function(){ $.fn.extend({ SimpleTree:function(options){ //初始化参数 var option = $.extend({ click:function(a){ } },options); option.tree=this; /* 在参数对象中添加对当前菜单树的引用,以便在对象中使用该菜单树 */ option._init=function(){ /* * 初始化菜单展开状态,以及分叉节点的样式 */ this.tree.find("ul ul").hide(); /* 隐藏所有子级菜单 */ this.tree.find("ul ul").prev("li").removeClass("open"); /* 移除所有子级菜单父节点的 open 样式 */ this.tree.find("ul ul[show='true']").show(); /* 显示 show 属性为 true 的子级菜单 */ this.tree.find("ul ul[show='true']").prev("li").addClass("open"); /* 添加 show 属性为 true 的子级菜单父节点的 open 样式 */ }/* option._init() End */ /* 设置所有超链接不响应单击事件 */ this.find("a").click(function(){ $(this).parent("li").click(); return false; }); /* 菜单项 接受单击 */ this.find("li").click(function(){ /* * 当单击菜单项 * 1.触发用户自定义的单击事件,将该 标签中的第一个超链接做为参数传递过去 * 2.修改当前菜单项所属的子菜单的显示状态(如果等于 true 将其设置为 false,否则将其设置为 true) * 3.重新初始化菜单 */ option.click($(this).find("a")[0]); /* 触发单击 */ /* * 如果当前节点下面包含子菜单,并且其 show 属性的值为 true,则修改其 show 属性为 false * 否则修改其 show 属性为 true */ /* if($(this).next("ul").attr("show")=="true"){ $(this).next("ul").attr("show","false"); }else{ $(this).next("ul").attr("show","true"); }*/ /* 初始化菜单 */ option._init(); }); /* 设置所有父节点样式 */ this.find("ul").prev("li").addClass("folder"); /* 设置节点“是否包含子节点”属性 */ this.find("li").find("a").attr("hasChild",false); this.find("ul").prev("li").find("a").attr("hasChild",true); /* 初始化菜单 */ option._init(); }/* SimpleTree Function End */ }); });
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值