首页 > 代码库 > Chapter 2 算法基础

Chapter 2 算法基础

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

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

----------------------------------------

2.1 插入排序

核心: 将一个数插入到已经排好序的有序数列中,从而得到一个新的、个数加1的有序数列

 形象描述:有两堆牌,左手一堆已经排好序,右手一堆未排好,将右手中的牌一张一张取出来,放到左手这堆牌中(保证每次放进去都使左手牌仍有序)

实现代码(以下按升序排序):为了以后偷懒,采用了泛型

        static void InsertSort<T>(List<T> array) where T:IComparable        {            for(int i=1;i<array.Count;i++)   //将第i个数插入已排好序的[0...i-1]中            {                T key = array[i];                int position = i;  //用于记录将要插入的位置                for(int j=i;j>=0;j--) //将要前面数与要插入数进行比较,如果比key大,往后移一位                {                    if(array[j].CompareTo(key)>0) //由于T实现了IComparable接口                    {                        array[j + 1] = array[j];                        position = j;                    }                }                array[position] = key;            }        }

 

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

技术分享
static void Main(string[] args)        {            Random rnd = new Random();            List<int> array=new List<int>();            for (int i = 0; i < 10;i++ )            {                array.Add(rnd.Next(50));            }            Console.WriteLine("排序前:");            foreach (int i in array)                Console.Write("{0} ",i);            Console.WriteLine();            InsertSort<int>(array);            Console.WriteLine("排序后:");            foreach (int i in array)                Console.Write("{0} ", i);            Console.ReadKey();        }
View Code

其中一次运行结果如下:

技术分享

-----练习----

2.1-2:只需将array[j].CompareTo(key)>0改为array[j].CompareTo(key)<0即可

2.1-3:线性查找(以int为例,此外略微改动了下原题),代码如下:

技术分享
        static int FindNum(List<int> array,int num)        {            int position = -1;  //记录下标作为返回值,未找到则为-1            for(int i=0;i<array.Count;i++)            {                if (array[i] == num)                {                    position = i;                    break;    //为了返回结果为第一次找到该值的下标                }            }            return position;        }
View Code

 

2.1-4:二进制相加问题的体现,2个n位的二进制相加,结果可能依旧为n位,或者n+1位

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

代码如下:(此代码有待改善,以下代码必须要求a,b的位数相同)

技术分享
        static List<int> AddBinary(List<int> a,List<int> b)        {            int[] c = new int[a.Count+1]; //数组c用于存储相加后的结果,长度为n+1            for(int i=a.Count-1;i>=0;i--)            {   //此处为两个二进数对应项及进位的值共同相加                if (a[i] + b[i] + c[i + 1] >= 2)   //进位的情况                {                    c[i + 1] = a[i] + b[i]+c[i+1] - 2;                     c[i]++;                }                else                              //不进位的情况                    c[i + 1] = a[i] + b[i]+c[i+1];            }            List<int> list = new List<int>();            list.AddRange(c);                return list;                         //返回相加结果        }
View Code

 

测试代码如下:

技术分享
        static void Main(string[] args)        {            int n = 6;            Random rnd = new Random();            List<int> a = new List<int>();            List<int> b = new List<int>();            for(int i=0;i<n;i++)            {                a.Add(rnd.Next(2));                b.Add(rnd.Next(2));            }            Console.Write("二进数A=");            foreach(int i in a)                Console.Write("{0}",i);            Console.WriteLine();            Console.Write("二进数B=");            foreach (int i in b)                Console.Write("{0}", i);            Console.WriteLine();            List<int> c = AddBinary(a, b);            Console.Write("   A+B=");            foreach(int i in c)                Console.Write("{0}", i);            Console.ReadKey();        }
View Code

 

一次测试实例结果:

 

技术分享

---------------

2.2 分析算法

判断算法优劣,根据算法所需要的资源----大部分情况下,我们采用运行时间

往往取运行时间的增长量级或增长率---即最高次作为衡量一个算法的"优劣"

 -------练习-------

2.2-2 选择排序:找出A中最小元素将其与A[0]交换位置,在找出除A[0]外最小元素与A[1]交换,一直重复到n-1

形象描述:在一堆鞋子中,找出尺码最小的往外扔,依次进行直到扔完(升序),扔完后鞋子叠起来的次序便是排好序的

排序实现代码如下:

技术分享
        static void SelectSort<T>(List<T> array)where T:IComparable        {            int position=0;            for(int i=0;i<array.Count-1;i++)            {                position = i;              //用于记录i之后最小值的下标                for(int j=i+1;j<array.Count;j++) //遍历找出i之后最小值                {                    if (array[j].CompareTo(array[position])<0)                        position = j;                }                T temp = array[position];   //交换位置                array[position] = array[i];                array[i] = temp;            }        }
View Code

 

运行情况如下:

技术分享

最坏最好情况分析:由于两个for循坏无论在什么前提下,均执行次数一致(可以看到没有break等语句在Code中),所以最好最坏情况的运行时间均为θ(n^2)。

2.2-3 线性查找问题:最坏n次,最好1次。最坏和平均情况的运行时间均为θ(n)。

--------------------

2.3 设计算法

分治法:简单的说就是把问题拆拆拆(前提:拆了的只是规模比原来小)!!!拆到你感觉解起来so easy的时候那就解吧,解完后再递归回去,麻烦点的话还可能要有怎么合并的考虑。

实例:递归排序---不断对半砍直到每个都只为1个,还需要排吗?哦啦,但是还需要考虑合并的问题啦。两堆有序之间合并采用直接一一对比!

合并函数(为了能用泛型,采用不含哨兵的方法---如何对于一个T设置一个很大的值还木有想到>.<!):

        static void Merge<T>(List<T> array,int p,int q,int r)where T:IComparable        {            int len1 = q - p + 1, n1 = 0;  //用于临时存储两边元素的数组的参数            int len2 = r - q, n2 = 0;            T[] arr1 = new T[len1];   //建立两个临时数组,用于储存数组值            T[] arr2 = new T[len2];            for (int i = 0; i < len1; i++)  //将array值拷贝到临时存放数组                arr1[i] = array[p + i];            for (int j = 0; j < len2; j++)                arr2[j] = array[j + q + 1];            for(int k=p;k<r+1;k++)  //将两个数组中的值按升序放回到array数组中            {                if (arr1[n1].CompareTo(arr2[n2]) < 0)                    array[k] = arr1[n1++];                else                    array[k] = arr2[n2++];                if(n1==len1)       //此处为arr1数组已全部取完后的处理方法                {                    for(int m=k+1;m<r+1;m++)                        array[m] = arr2[n2++];                    break;                }                if(n2==len2)                {                    for (int m = k+1; m < r + 1; m++)                        array[m] = arr1[n1++];                    break;                }            }                   }

 

 

递归排序函数:

        static void MergeSort<T>(List<T> array, int p, int r) where T : IComparable        {            if(p<r)            {                int q = (p + r) / 2;                MergeSort(array, p, q);                MergeSort(array, q + 1, r);                Merge(array, p, q, r);            }        }

 

运行时间为O(nlogn)---由于合并函数的缘故,对于数量少的情况反而会更加耗时

-------练习-------

2.3-3 显然当n=2时,T(2)=2lg2=2---成立

         假设当n=2k时,T(2k)=2klg(2k)成立,只需证T(2k+1)=2k+1=2k+1lg2k+1

 

         由  T(2k+1)=2T(2k)+2k+1=2*2klg(2k)+2k+1=2k+1(lg2k+lg2)=2k+1lg2k+1

         从而得证。

2.3-4 插入排序的递归模式,插入函数+递归函数

插入函数如下:(和插入排序进行对比,少了个for语句---由递归完成)

技术分享
        static void Insert(List<int> array,int p,int q)        {            int temp = array[q], position = q;  //将array[q]插入到p..q-1中,使之依然满足升序            for(int i=q-1;i>=p;i--)            {                if (array[i] > temp)                {                    array[i + 1] = array[i];                    position = i;                }                else                          //为了减少运行步数,此处采用了break                    break;            }            array[position] = temp;        }
View Code

 

递归函数

技术分享
        static void InsertMerge(List<int> array,int p,int r)        {            int q;            if(p<r)            {                q = r - 1;                InsertMerge(array, p, q);                Insert(array, p, r); //此处注意为r,如果改为q的话,则最后一项未调用排序函数            }        }
View Code

 

最坏运行时间的递归式为:技术分享

2.3-5 二分查找(以下Code只适用于已排好序的情况)

技术分享
        static int BinarySearch(List<int> array,int p,int r,int num)        {            int q = (r + p) / 2;            if (num == array[q])                return q;            else if (num > array[q]&&q<r)                return BinarySearch(array, q+1, r, num);              else if(num<array[q]&&q>p)                return BinarySearch(array, p, q-1, num);            return -1;        }
View Code

 

2.3-6 不能,原因:利用二分查找改良的插入排序:但是由于插入位置后续的数组均要向后移动,因此最坏情况运行时间仍旧为θ(n2)

2.3-7 确定n个整数的集合S中是否存在两个数之和刚好为x的元素

method1:先将S中集合进行排序,采用递归或者后续其他排序(时间为θ(nlgn)),然后再利用二分查找+for进行寻找(该方法时间也为θ(nlgn))

递归排序代码与前面的完全相同,略过

二分查找+遍历循坏:

技术分享
        static List<int> BinaryFind(List<int> array,int num)        {            List<int> list = new List<int>(2);            for(int i=0;i<array.Count-1;i++)   //将0...n-1分别与后续的进行相加            {                int begin = i + 1,end = array.Count - 1;  //进行二分查找的首尾                int mid=(begin+end)/2;                              while(begin<=end)                        //循环的终止条件,即二分查找的终止条件                {                    if (array[i] + array[mid] == num)    //找到的情况                    {                        list.Add(array[i]);                        list.Add(array[mid]);                        return list;                    }                    else if (array[i] + array[mid] > num)                         end = mid-1;                    else                        begin = mid+1;                    mid = (begin + end) / 2;                }            }            list.Add(-1); list.Add(-1);  //找不到的情况            return list;        }
View Code

 

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

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

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

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

  step 4:将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;            }        }
View Code

 

测试代码如下:

技术分享
        static void Main(string[] args)        {            List<int> array = new List<int> { 12, 3, 10, 1, 2, 4, 5, 8, 6, 7};            Random rnd = new Random();            int num =rnd.Next(25);            foreach (int a in array)                Console.Write("{0} ", a);            Console.WriteLine();            Console.WriteLine("num={0}", num);            MergeSort<int>(array, 0, array.Count - 1);            List<int> result = FindSum(array, num);            if (result[0] == -1)                Console.WriteLine("该数组中找不到!");            else            {                Console.WriteLine("此数组中存在两数之和为{0},且存在于以下几个数中", num);                foreach (int a in result)                    Console.Write("{0} ", a);            }            Console.ReadKey();        }
View Code

 

其中一次运行结果如下:技术分享

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

----------------

思考题:待续

Chapter 2 算法基础