Chapter2 Getting Started

本文深入探讨了多种排序算法,包括插入排序、选择排序、归并排序等,并提供了详细的C#实现代码。通过具体实例展示了算法的工作原理及效率分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2.1 插入排序

核心思想: 依次从未排序的数组中取出数,并将该数插入到有序数组中,并保持有序数组依然有序。(初始状态,有序数组仅一个数开始)—算法导论里给出的形象比喻:左手扑克牌已排好序,右手扑克牌未排序,从右手依次从左到右取出牌放入左手牌中并保证左手的拍依旧有序。
C#代码(升序):

public void InsertSortUp(List<int> array)
{
    int key, j; //key用来代表取出的值
    for (int i = 1; i < array.Count; i++)
    {
        key = array[i];
        //insert array[i] into the sorted array[1...j-1]
        j = i - 1;
        while (j >=  0 && array[j] > key)
        {
            array[j + 1] = array[j];
            j--;
        }
        array[j + 1] = key;
    }
}

Note: 以上易错在于while循环部分将大于key的后移一位。
Exercise:

2.1-1
<31><41,59,26,41,58>→<31,41><59,26,41,58>→<31,41,59><26,41,58>→<26,31,41,59><41,58>→<26,31,41,41,59><58>→<26,31,41,41,58,59>

2.1-2
将上述升序代码中的array[j]>key改为array[j]<key便可实现降序排序

2.1-3
线性查找问题:
 //if num=array[i],return i, else return -1;
 public int LSearch(List<int> array,int num)
 {
     for(int i=0;i<array.Count;i++)
     {
         if (num == array[i])
             return i;
     }
     return -1;
 }

2.1-4
二进制数相加问题:
 //A+B=C  (均为二进制数以及A,B的长度相等,不足用0添)
 public int[] Add(int[] A,int[] B,int n)
 {
     int temp=0,sum=0;
     int[] C=new int[n+1];
     for(int i=n-1;i>=0;i--)
     {
         sum = A[i] + B[i]+temp; //temp用于代表进位值
         if (sum >= 2)   //存在进位情况
         {
             C[i + 1] = sum - 2;
             temp = 1;
         }
         else           //不存在进位情况
         { 
             C[i+1] = sum;
             temp = 0;
         }
     }
     C[0] = temp;       //C比A和B多一位(该位也可能为0)
     return C;
 }
**Note:二进制存入数组是越高位处于数组下标越小处。此外,可进一步改善为对任意二进制均适用的加法。**

2.2 分析算法

Exercise:

2.2-1
n3/1000100n2100n+3
表示:θ(n3)

2.2-2
选择算法(依次找出最小的”往前扔”):

 public void Selectsort(List<int> array)
 {
     int min, pos;
     for (int i = 0; i < array.Count-1; i++)
     {
         min = array[i];   //min用于存储最小值
         pos = i;          //pos用于存储最小值的下标
         for (int j = i+1; j < array.Count; j++)
         {
             if (array[j] < min)
             {
                 min = array[j];
                 pos = j;
             }
         }
         array[pos] = array[i];
         array[i] = min;
     }
 }

最好情况最坏情况运行时间均为:θ(n2)

2.2-3
线性查找问题(要查找元素等可能地为数组中的任意元素):
平均需检查输入序列元素数: (1+n)(n/2)/n=(n+1)/2
最坏情况需检查输入序列元素数:n
平均情况与最坏情况均为 θ(n)

2.2-4
尽量减少循环次数

2.3 设计算法

分治模式的三个步骤:

  • 分解:将原问题分解成若干子问题,这些子问题均为原问题规模较小的实例。
  • 解决:递归地求解各子问题,当子问题规模足够小时直接求解。
  • 合并:合并这些子问题的解成原问题的解。

归并排序:
合并(将两个规模小的有序数组合并为大的有序数组)

 //含有哨兵的情况,arr1[n1]和arr2[n2]均为哨兵
 private void Merge(List<int> array,int begin,int mid,int end)
 {
     int n1 = mid - begin+1;
     int n2 = end - mid;       //输入的end代表数组最后一项而非数组长度
     int[] arr1 = new int[n1+1];
     int[] arr2 = new int[n2+1];
     for (int i = 0; i < n1; i++)  //赋值
         arr1[i] = array[begin+i];  
     for (int i = 0; i < n2; i++)
         arr2[i] = array[mid+i+1];
     arr1[n1] = Int16.MaxValue;     
     arr2[n2] = Int16.MaxValue;
     int m = 0, n = 0;
     for (int k = begin; k <=end; k++)  //依次取出两数组中较小值,此处为核心
     {
         if (arr1[m] < arr2[n])
         {
             array[k] = arr1[m];
             m++;
         }
         else
         {
             array[k] = arr2[n];
             n++;
         }
     }
 }

分解+解决(将n分解为两个n/2并递归地排列两个子序列):

public void Mergesort(List<int> array,int begin,int end)
{
    int mid;
    if(begin<end)   //直到每个子数组均只有单个值时停止分解进行求解
    {
        mid = (begin + end) / 2;
        Mergesort(array, begin, mid);
        Mergesort(array, mid+1, end);
        Merge(array, begin, mid, end);
    }
}

Exercise:
2.3-1
2.3-2
不使用哨兵的合并程序:

//无哨兵情况,利用continue语句.
private void Merge2(List<int> array, int begin, int mid, int end)
{
    int n1 = mid - begin + 1;
    int n2 = end - mid;
    int[] arr1 = new int[n1];
    int[] arr2 = new int[n2];
    for (int i = 0; i < n1; i++)
        arr1[i] = array[begin + i];
    for (int i = 0; i < n2; i++)
        arr2[i] = array[mid + 1 + i];
    int m = 0, n = 0;
    for (int k = begin; k <= end; k++)
    {
        if (m == n1)   //此判断语句来取代哨兵
        {
            array[k] = arr2[n];
            n++;
            continue;
        }
        if (n == n2)
        {
            array[k] = arr1[m];
            m++;
            continue;
        }
        if (arr1[m] < arr2[n])
            array[k] = arr1[m++];
        else
            array[k] = arr2[n++];
    }
}

2.3-3
数学归纳法:
step1: 当n=2时,左边(T(2)=2)=右边(T(2)=2lg2=2)成立。
step2: 不妨假设n=2k时 T(n)=nlgn成立。
step3: 当n=2k+1时,T(n)=2T(n/2)+n=2T(2k)+2k+1=22klg2k+2k+1=(k+1)2k+1=nlgn,所以成立,得证!

2.3-4
插入排序的递归描述:

//note:此处的end代表的是数组的长度
public void InsertSort(List<int> array,int begin,int end)
{
    if(begin<end)
    {
        end--;
        InsertSort(array, begin, end);
        int key=array[end],i;
        for(i=end-1;i>=begin;i--)
        {
            if (array[i] > key)
                array[i + 1] = array[i];
            else
                break;
        }
        array[i+ 1] = key;
    }
}

2.3-5
二分查找:

public int BinarySearch(List<int> array,int begin,int end,int key)
{
    int mid=(begin+end)/2;
    while(mid!=begin&&mid!=end)
    {
        if (array[mid] == key)
            return mid;
        else if (array[mid] < key)
            begin = mid;
        else
            end = mid;
        mid = (begin + end) / 2;
    }
    return -1;  //-1代表没有找到
}

最坏运行时间:假设 2m=n,则最多只能进行m=lgn次对分,所以最坏运行时间θ(lgn)

2.3-6
不可以,由于每次要将比key大的数后移一位,所以不可避免的需要进行m次移动。

2.3-7
n个整数的集合S和另一个整数x,确定S中是否存在两个其和刚好为x的元素: 且要求运行时间为θ(nlgn)—采用归并排序+二分查找
二分查找部分:

private int FindKey(List<int> array,int begin,int end,int key)
{
    int mid=(begin+end)/2;
    while(mid!=begin&&mid!=end)
    {
        if (array[mid] == key)
            return mid;
        else if (array[mid] < key)
            begin = mid;
        else
            end = mid;
        mid = (begin + end) / 2;
    }
    return -1;
}

归并+整体查找

public List<int> FindNum(List<int> array,int num)
{
    MergeSort sort = new MergeSort(); //归并排序
    sort.Mergesort(array, 0, array.Count - 1);
    int key;
    int[] result = new int[array.Count];
    List<int> res=new List<int>();
    for(int i=0;i<array.Count;i++)
    {
        key = num - array[i];
        result[i]=FindKey(array,0,array.Count-1,key);
    }
    for(int i=0;i<result.Length/2;i++)  //为了避免重复采取的措施
    {
        if (result[i] != -1&&res.Contains(result[i])==false)
            res.Add(i);
    }
    return res;
}

Note: 还有更精妙的解法(但相对需要数学知识)

思考题

2-1 归并排序+插入排序
在归并排序中对小数组采用插入排序—-使用插入排序来排序长度为k的n/k个子表。
插入排序(同文章开头的插入排序)
归并排序中的合并(采用上述中无哨兵的合并)
具体的分解+解决:

public void MSort(List<int> array, int begin, int end,int k)
{
    int mid = (begin + end) / 2; ;
    if ((end-begin)>k)  //当两者的间距小于等于k时采用插入排序
    {
        MSort(array, begin, mid,k);
        MSort(array, mid + 1, end,k);
    }
    else
        InsertSort(array, begin, end);
    Merge2(array, begin, mid, end);            
}

a. 证明:每个长度为k的子序列进行插入排序需要时间 (k1)+(k2)++1=k(k1)2 从而n/k个长度为k的子表需要时间为 k(k1)2nk=n(k1)2=θ(nk)

b. 从n个数到n/k个k长度的数需要划分次数为:n2m=km=lgnk,即共有lgnk层,而每层所需时间为n,所以合并这些子表所需时间为 θ(nlgnk).

c. 易知θ(nlgnk)<θ(nlgn),因此,只需θ(nk)<θ(nlgn)便可,所以k<lgn

d. k的选择,当输入数据较好时,插入排序耗时少k选择大,否则k选择小。

2-2 冒泡排序

public void Bubblesort(List<int> array)
{
    int temp;
    for(int i=0;i<array.Count;i++)
        for(int j=array.Count-1;j>i;j--)
        {
            if(array[j]<array[j-1])
            {
                temp = array[j];
                array[j] = array[j - 1];
                array[j - 1] = temp;
            }
        }
}

冒泡排序的最坏情况运行时间:θ(n2).

2-3 略

2-4 逆序对
定义: A[1…n]是一个有n个不同数的数组,若i<j,且A[i]>A[j],则对偶(i , j)称为A的一个逆序对。
a. <2, 3, 8, 6, 1>的5个逆序对(2, 1), (3, 1), (8, 6), (8, 1), (6, 1)
b. 由集合{1, 2, …, n}中的元素构成的降序排序数组具有最多的逆序对,有n(n1)2对。
c. 成正比,插入时间越长,逆序对的数量越多(此处插入排序是指升序排序)
d. 求逆序对数量的算法:合并时即进行排序同时记录每个子例中逆序对个数。

//归并算法求每个子数组中逆序对个数并进行排序
private int Merge2(List<int> array, int begin, int mid, int end)
{
    int n1 = mid - begin + 1;
    int n2 = end - mid;
    int num = 0;
    int[] arr1 = new int[n1];
    int[] arr2 = new int[n2];
    for (int i = 0; i < n1; i++)
        arr1[i] = array[begin + i];
    for (int i = 0; i < n2; i++)
        arr2[i] = array[mid + 1 + i];
    int m = 0, n = 0;
    for (int k = begin; k <= end; k++)
    {
        if (m == n1)
        {
            array[k] = arr2[n];
            n++;
            continue;
        }
        if (n == n2)
        {
            array[k] = arr1[m];
            m++;
            continue;
        }
        if (arr1[m] < arr2[n])
            array[k] = arr1[m++];
        else
        {
            array[k] = arr2[n++];
            num = num+(n1-m);    //记录逆序对个数
        }
    }
    return num;
}

//获取逆序对总数量
public void MSort(List<int> array, int begin, int end)
{
    int mid;
    if (begin < end)
    {
        mid = (begin + end) / 2;
        MSort(array, begin, mid);
        MSort(array, mid + 1, end);
        int num=Merge2(array, begin, mid, end);
        result += num;
    }
}
//返回结果
private static int result;
public int Getresult()
{
    return result;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值