代码改变世界

算法与数据结构——排序(八)归并排序的非递归实现

2012-11-18 16:08  左眼微笑右眼泪  阅读(757)  评论(0编辑  收藏  举报

     前面我们用递归的方法实现了归并排序,递归比较占用内存空间,能不能用非递归的方法来实现归并排序呢。答案当然是可以的。

     使用非递归排序的思路如下(以序列{0,4,8,9,7,1,3,9,2}为例):

image

1.首先s=1,把数组中数两两归并,并放入一个新数组中(一般就拿它本身的那个数组来放),如果最后多一个数(例如最后一个数字2单独为一组),那么直接把这个数加入到新数组中去。这步完成后,每相邻的两个数内部是排好了序的,在这一步里面,会对9个序列(每个数是一个序列)进行归并,最终得到5个序列。

2.第二步s=2,把第1到第4个数,第5到第8个数进行归并,最后一个数字2又是单独的一个序列,不参与归并。第二步完成后,从第一个数开始,每相邻的4个数是排好序了的,在这一步里面会对5个序列(每2个数一个序列,最后一个数单独一个序列)进行归并,最终得到3个序列。

3.第三步s=4,把第1到8个数进行归并,最后一个数字2又是单独的一个序列,归并完成后,前面8个数内部是有序的,这一步里面会对3个序列(每4个数一个序列,最后一个数单独一个序列)进行归并,最终得到2个序列。

4第四步s=8,把第1到16个数进行归并,这时候最后一个数字2就参与到归并中去了,归并完成后,整个序列就都是有序的了,这一步里面会对两个序列(每8个数一个序列,最后一个数单独一个序列)进行归并,最终得到一个序列。

      下面看具体的代码实现:

      首先是通用的合并两个有序list的方法:

/// <summary>
/// 把两个有序的序列A和B合并到一个有序列temp里面
/// </summary>
/// <param name="sortListA">有序序列A</param>
/// <param name="sortListB">有序序列B</param>
/// <param name="tempList">有序序列Temp</param>
public void Merge(List<int> sortListA, List<int> sortListB, List<int> tempList)
{
    int countB = 0;
    int countA = 0;
    //1.以某个序列为原始序列,把它的每一个数,与另外的一个序列做比较
    for (int i = 0; i < sortListA.Count; i++)
    {
        //1.1循环遍历序列B的每一个序列(因为B序列是有序列的,所以第一个元素是最小的)
        for (int j = countB; j < sortListB.Count; j++)
        {
            //1.2如果A序列中的某个元素比B中的小,就把A中的这个元素加到temp中去
            if (sortListA[i] < sortListB[j])
            {
                tempList.Add(sortListA[i]);
                countA++;//记录加到temp中去的A序列中元素的数量 
                break;
            }
                //1.2否则把B中的元素加到temp中去
            else
            {
                tempList.Add(sortListB[j]);
                countB++;//记录加到temp中去的B序列中元素的数量 
            }
        }
    }//注意,此for循环结束后,一定有一个序列中的元素全部加到了temp中去了。
 
    //如果此处不相等,代表B中的元素还没有加完,那么把B中剩下的元素加入到Temp中去
    if (countB != sortListB.Count)
    {
        for (int k = countB; k < sortListB.Count; k++)
        {
            tempList.Add(sortListB[k]);
        }
    }
    //否则的话,代表A中的元素还没有加完,那么把A中剩下的元素加入到Temp中去
    else
    {
        for (int k = countA; k < sortListA.Count; k++)
        {
            tempList.Add(sortListA[k]);
        }
    }
}

    然后来看主方法,在这个方法里面采用循环,实现上面的四步归并:

public List<int> SortByMergeNew(List<int> sortList)
{
    List<int> resultList=new List<int>();
    int i = 1;
    while (i<sortList.Count)
    {
        resultList.Clear();
        MergePass(sortList,resultList,i,sortList.Count);
        i = 2*i;
        sortList.Clear();
        MergePass(resultList, sortList, i, resultList.Count);
        i = 2*i;
    }
    return sortList;
}

   里面调用了MergePass方法,这个方法的作用就是具体实现上面的每一步,在这个例子里面,这个方法会被调用4遍。

public void MergePass(List<int> sortList,List<int> resultList,int s,int listLength )
{
    int i = 0;
    int sumCount = listLength - 2*s;
 
    while (i<=sumCount)
    {
        MergeList(sortList, resultList, i, i + s, i + 2 * s);
        i = i + 2*s;//i每次要增加2s
    }
    if (i < listLength - s)
    {
        //如果有单数的话,把最后的两个序列归并,(如果有9个数的话,这句话就是把前面8个数与最后一个数归并)
        MergeList(sortList, resultList, i, i + s, listLength);
    }
    else//如果后面有剩余的数,则追加到数组中,如果没有,则不追加
    {
        for (int k = i; k < listLength; k++)
        {
            resultList.Add(sortList[k]);
        }
    }
}

   在上面的4个步骤里面,每一步,实现上是对多对序列进行归并的,每对序列的归并是通过MergeList进行实现的,MergeList的作用是产生每个序列,产生后调用最前面的那个Merge方法来实现真正的归并的。

private void MergeList(List<int> sortList, List<int> resultList, int start, int mid,int end)
{
    var listA = new List<int>();
    var listB = new List<int>();
 
    //如果s等于1的时候,下面的代码是把第0,1//4,5,//8,9装入到一个List中去
    for (int j = start; j < mid; j++)
    {
        listA.Add(sortList[j]);
    }
    //如果s等于1的时候,下面的代码是把第2,3//6,7装入到一个List中去
    for (int k = mid; k < end; k++)
    {
        listB.Add(sortList[k]);
    }
    Merge(listA, listB, resultList);//把两个List进行合并
}

   通过非递归的方法来实现归并排序,减少了空间的复杂度,空间复杂度为O(n),在时间性能上也有一定的提升。