码迷,mamicode.com
首页 > 其他好文 > 详细

Chapter2 Getting Started

时间:2015-03-12 09:41:41      阅读:119      评论:0      收藏:0      [点我收藏+]

标签:

-------------------注明----------------

以下内容来自于《算法导论》           lz新手,存在各种错误以及各种不合理的地方望大家指出


2.1 插入排序

核心: 将一个数插入到已经排好序的有序数列中,从而得到一个新的、个数加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的后移一位。

采用的测试代码如下:(以int类型为示范)

static void Main(string[] args)
        {
            List<int> array = new List<int> { 1, 23, 51, 21, 31, 23, 12 };
            InsertSort Sort = new InsertSort();
         
            Console.WriteLine("the original array is:");
            foreach (int a in array)
                Console.Write("{0} ", a);
            Console.WriteLine();
            //Ascending Sort
            Sort.InsertSortUp(array);
            Console.WriteLine("the sorted array is:");
            foreach (int a in array)
                Console.Write("{0} ", a);
            Console.WriteLine();
            Console.ReadKey();
        }

运行结果如下:

技术分享

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
二进制数相加问题:
核心:将二进制数存进数组,如0111,也按常规数组思路,从左到右代表数组的下标(即0存储在A[0]中),根据对应项相加,遇2进1的原则。
//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

技术分享 表示技术分享

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;
     }
 }

最好情况最坏情况运行时间均为:技术分享

2.2-3

线性查找问题(要查找元素等可能地为数组中的任意元素):
平均需检查输入序列元素数: 技术分享
最坏情况需检查输入序列元素数: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: 不妨假设技术分享时 T(n)=nlgn成立。
step3: 当技术分享时,技术分享,所以成立,得证!

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代表没有找到
}

最坏运行时间:假设 技术分享,则最多只能进行技术分享次对分,所以最坏运行时间 θ(lgn)

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

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

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;
}

method2:此方法来源于一篇博客(当时做的笔记,未写引用,忘见谅)

step1:对集合S进行排序,可以采用归并排序算法

step2:对S中每一个元素a,将b=x-a构造一个新的集合S‘,并对S’进行排序

step3:去除S和S‘中重复的数据

step4:将S和S‘按照大小进行归并,组成新的集合T,若干T中有两队及以上两个连续相等数据,说明集合S中存在两个整数其和等于x。

  • 例如:S={7,10,5,4,2,5},设x=11,执行过程如下:
  • 对S进行排序,S={2,4,5,5,7,10}。
  • S‘={9,7,6,6,4,1},排序后S‘={1,4,6,6,7,9}。
  • 去除S和S‘中重复的数据后S={2,4,5,7,10},S‘={1,4,6,7,9}

归纳S和S‘组成新集合T={1,2,4,4,5,6,7,7,9,10},可以看出集合T中存在两对连续相等数据4和7,二者存在集合S中,满足4+7=11。

step 1根据上述递归排序,step2~step4的Code如下:

static List<int> FindSum(List<int> array,int num)
        {
            List<int> arr = new List<int>();
            List<int> list = new List<int>();
            for (int i = 0; i < array.Count; i++) //一个新数组,每个元素b[i]=num-a[i]
                arr.Add(num - array[i]);
            for (int i = 0; i < array.Count-1; i++)
            {
                if (array[i] == array[i + 1])
                    array.RemoveAt(i);
                if (arr[i] == arr[i + 1])
                    arr.RemoveAt(i);
            }
            for (int i = 0, j = arr.Count - 1; i < array.Count || j >= 0;) 
            {  //将arr和array组合成新集合
                if(i==array.Count)
                {
                    for (int k = j; k >= 0; k--)
                        list.Add(arr[k]);
                    break;
                }
                if(j==-1)
                {
                    for(int k=i;k<array.Count;k++)
                        list.Add(array[k]);
                    break;
                }
                if (array[i] > arr[j])
                    list.Add(arr[j--]);
                else
                    list.Add(array[i++]);
            }
            int count = 0;          //用于记录有多少对重复
            List<int> result = new List<int>();
            for(int i=0;i<list.Count-1;i++)  
            {
                if (list[i] == list[i + 1])
                {
                    count++;
                    result.Add(list[i]);
                }
            }
            if (count >= 2)
                return result;
            else
            {
                result.Clear();
                result.Add(-1);
                return result;
            }
        }

此方法的原理:b=x-a,如果数组S和S‘中均有a,b则显然会出现两个连续相等数据(并且此时a,b恰好为所需值)(注,由于此方法同时将b=x-a,b==a的情况也包含进去,从而会出现单数次重复数据---如果排除此情况必然为双数次重复)

思考题

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的子序列进行插入排序需要时间 技术分享 从而n/k个长度为k的子表需要时间为 技术分享

b. 从n个数到n/k个k长度的数需要划分次数为:技术分享,即共有技术分享层,而每层所需时间为n,所以合并这些子表所需时间为技术分享

c. 易知技术分享因此,只需技术分享便可,所以技术分享

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;
            }
        }
}

冒泡排序的最坏情况运行时间:技术分享

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}中的元素构成的降序排序数组具有最多的逆序对,有技术分享对。
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;
}

Chapter2 Getting Started

标签:

原文地址:http://www.cnblogs.com/Paul-chen/p/4331488.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!