算法导论记录本

2014/11/6
算法导论,插入排序:

过程描述:待排序序列数分为两部分,一部分为已排好序序列,一部分未排好,初始时,已排好个数为1。

从未排好序序列第一个数开始循环,如果已排好序列中的数大于第一个数,将该数右移,直到

遇见小于等于第一个数的数值,那么在该位置插入为未排序序列中的第一个数,之后的从第二

个数开始,重复过程,直到遍历完所有的数。

其中已排好序系列称为循环不变式,只要证明循环不变式在三个条件下成立,那么该算法设计就

是正确的,这三个条件分别是初始化保持终止。


插入排序伪代码过程如下:

顺序排列
INSERT-SORT(A)
for j = 1 to A.length
	key = a[j]
        i = j -1 
	while(A[i] > key && i > -1)
		A[i+1] = A[i]
		i = i - 1
	A[I+1] = key


算法分析:插入排序的思想基于增量法,将[0~j-1]排序后,将单个元素A[j]插入子数组的合适位置,产生

新的排序子序列,直到子序列长度和原序列一样。


2014/11/6
算法导论,归并排序:

算法分析:归并排序是基于分治的思想,将一组序列的排序分解成多个更小的序列排序,然后从最小的序列

(长度为1的序列)开始递归向上求解,求解过程是对左右分治的已排好序序列进行比较,填充上一级序列。

以下为含四个数的序列,排序流程图:



排序算法C语言实现如下:

有一个小技巧需要注意,在合并流程里面,在每一个待合并序列的末端加入一个无穷大的数,

这样免去了数组为空的判断逻辑。

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <time.h>
#define MAXSIZE 4
#define ENDLESS 0xFFFFFFF

/*合并流程*/
void Merge(int *a,int l,int m,int r);
/*递归流程*/
void MergeSort(int *a,int l,int r);
void PrintInfo(int *a,int n);

int main()
{
    int *a = (int *)malloc(sizeof(int)*MAXSIZE);
    srand(time(0));
    for(int i=0;i<MAXSIZE;i++)
    {
        a[i] = rand()%100;
    }
    PrintInfo(a,MAXSIZE);

    MergeSort(a,0,MAXSIZE-1);
    PrintInfo(a,MAXSIZE);
    return 0;
}

void MergeSort(int *a,int l,int r)
{
    if(l < r)
    {
        int mid = (l + r)/2;
        MergeSort(a,l,mid);
        MergeSort(a,mid+1,r);
        PrintInfo(a,MAXSIZE);
        Merge(a,l,mid,r);
    }
}

void Merge(int *a,int l,int m,int r)
{
    int nl = m - l + 1;
    int nr = r - m;
    int *left = (int *)malloc(sizeof(int)*(nl+1));
    int *right = (int *)malloc(sizeof(int)*(nr+1));

    int i,j,k=l;
    for(i=0;i<nl;i++)
    {
        left[i] = a[l+i];
    }
    for(j=0;j<nr;j++)
    {
        right[j] = a[m+j+1];
    }
    left[i] = ENDLESS;
    right[j] = ENDLESS;
    i=j=0;
    PrintInfo(left,nl+1);
    PrintInfo(right,nr+1);


    for(k=l;k<=r;k++)
    {
        if(left[i] <= right[j])
        {
            a[k] = left[i];
            i++;
        }
        else
        {
            a[k] = right[j];
            j++;
        }
    }
}

void PrintInfo(int *a,int n)
{
    printf("Array info:");
    for(int i=0;i<n;i++)
    {
        printf("%d ",a[i]);
    }
    printf("\n");
}


2014/11/7
算法导论,分治策略,最大子数组和问题


n个数据最大子数组问题等价于,2个n/2最大子数组问题,2个n/2子数组问题又可以继续递归,直到问题分解

为1个数据的最大子数组问题,总共分解了lgn次。此时,最大子数组为自身.但是向上回朔求解子数组时候可

能有三种情况:

1.子数组左右边界都在左边部分

2.子数组左右边界都在右边部分

3.子数组跨越左右子集

基于这种思想,可以设计递归函数,分解成三部分问题.FIindCrossSubArray()求解跨越左右子集

问题的函数,跨越左右子集的边界计算是线性非递归的过程.


#include <stdio.h>
#include <limits.h>
#include <malloc.h>
#define MAXSIZE 4
/*分治发求解最大子数组问题*/

int *FindCrossSubArray(int *a,int low,int mid,int high)
{
    int leftSum = INT_MIN;
    int left = 0;
    int sum = 0;

    for(int i=mid;i>=0;i--)
    {
        sum = sum + a[i];
        if(sum > leftSum)
        {
            leftSum = sum;
            left = i;
        }
    }

    sum = 0;
    int rightSum = INT_MIN;
    int right = 0;
    for(int i=mid+1;i<=high;i++)
    {
        sum = sum + a[i];
        if(sum > rightSum)
        {
            rightSum = sum;
            right = i;
        }
    }

    int r[3] = {left,right,leftSum+rightSum};
    return r;
}

int *FindMaxSubArray(int *a,int low,int high)
{
    if(low == high)
    {
        int r[3] = {low,high,a[low]};
        return r;
    }
    else
    {
        int mid = (low + high)/2;
        int *left = FindMaxSubArray(a,low,mid);
        int *right = FindMaxSubArray(a,mid+1,high);
        int *cross = FindCrossSubArray(a,low,mid,high);

        if(left[2] >= right[2] && left[2] >= cross[2])
        {
            return left;
        }
        else if(right[2] >= left[2] && right[2] >= cross[2])
        {
            return right;
        }
        else
        {
            return cross;
        }
    }
}
/*
非递归实现
*/
int *FindMaxSubArray1(int *a,int low,int high)
{
    int maxSum = INT_MIN;
    int sum = 0;
    int start = 0;
    int end = 0;
    for(int i=0;i<=high;i++)
    {
        sum = sum + a[i];
        if(sum > maxSum)
        {
            maxSum = sum;
            end = i;
        }

        if(sum < 0)
        {
            sum = 0;
            start = i;
        }
    }

    int r[3] = {start,end,maxSum};
    return r;
}

int main()
{
    int *a = (int *)malloc(sizeof(int)*MAXSIZE);
    int low = 0;
    int high = MAXSIZE - 1;
    srand(time(0));
    printf("\nArray info : ");
    for(int i=0;i<MAXSIZE;i++)
    {
        a[i] = -rand()%20 + 10;
        printf("%d ",a[i]);
    }

    int *r = FindMaxSubArray1(a,low,high);
    printf("\nMax sub array info : left.%d,right.%d,sum.%d\n",r[0],r[1],r[2]);
    free(a);
    return 0;
}


Q.如果数组A全为负数,算法将产生什么结果?


分析:当A全为负数时,从最底层l=r=0开始求解,因为求解cross的时候,

无论往左还是往右都是负数,所以第lgn-1层的最大子数组就是lgn层的左

右子数组中最大的那个负数。

递归到第0层,就是k<(n-1)/2和k>(n-1)/2+1中的最大的那个负数。


lgn层

{ F(a,0,0) => r{0,0,a[0] }

{ F(a,1,1) => r{1,1,a[1] }

{ F(a,2,2) => r{2,2a[2] }

{ F(a,n-1,n-1) => r{n-1,n-1,a[n-1] }

lgn-1层

{ F(a,0,1) => r{max{F(a,0,0),F(a,1,1),cross(a,0,1)}}

{ F(a,2,3) => r{max{F(a,2,2),F(a,3,3),cross(a,2,3)}}

{ F(a,n-4,n-3) => r{max{F(a,n-4,n-4),F(a,n-3,n-3),cross(a,n-4,n-3)}}

{ F(a,n-2,n-1) => r{max{F(a,n-2,n-2),F(a,n-1,n-1),cross(a,n-2,n-1)}}

.......

0层(根)

F(a,0,n-1) =>r{max{F(a,0,n/2-1/2),F(a,n/2-1/2+1,n-1),cross(a,0,n-1)}}


 

2014/11/9
第二部分,排序和统计量,堆排序


七种排序算法的运行时间表

算法 最坏运行时间 平均情况/期望运行时间

插入排序 o(n平方) o(n平方)
归并排序 o(nlgn) o(nlgn)
堆排序 o(nlgn)
快速排序 o(n平方) o(nlgn)
计数排序 o(k+n) o(k+n)
基数排序 o(d(k+n)) o(d(k+n))
桶排序 o(n平方) o(n)

堆排序结合了归并排序和插入排序的优点,在运行时间上和归并相同,但是空间耗费和插入排序一样,
具有原址性,即排序过程只需要常数个额外的临时储存空间。

1)
(二叉)堆定义:堆近似被看成二叉数,树上每一个结点对应一个数组元素,除了最底层外,该树
是个完全二叉树。
对于结点i,它的父结点数组下标为i/2,左儿子结点为i *2,右结点为i*2+1(对应0开始的数组需要加1)。

最大堆:对于结点i,A[PARENT(i)] >=A[i]

最小堆:对于结点i,A[PARENT(i)] <=A[i]

2)
最大堆排序过程分三个:
1.MAX-HEAPIFY
保持最大堆的最大性,对于每一个根结点,它的左右孩子(如果有的话)的值都应比它要小,存在一个
循环不变量i,任意i<n-1有,A[i] > A[LEFT(i)] && A[i] > A[RIGHT(i)] 。伪代码过程如下:
MAX-HEAPIFY(H,i)
l = LEFT(i)
r = RIGHT(i)
if i<=h.heap-size && H[i] < H[l]
	largest = l
else largest = i
if i<=h.heap-size && H[largest] < H[r]
	largest = r

if i != largest
	MAX-HEAPIFY(A,largest)


2.BUILD-HEAP
创建最大堆。创建最大堆的流程就是从最底层的第一个元素(下标为n/2)开始,向上回朔根结点
循环调用MAX-HEAPIFY调整根结点和子结点的位置。其中存在一个循环不变量i,对于i+1,i+2....n
都是一个最大堆的根结点。伪代码过程如下:
BUILD-HEAP
A.heap-size = A.length
for i=n/2 down to 0
	MAX-HEAPIFY(A,i)

3.HEAP-SORT
堆排序
根据最大堆根结点为最大值的性质,从堆最底层的最后一个元素(下标n-1)开始,到第二个元素。
将该元素和堆顶(i=0)交换,然后由于根的改变,重新调整堆元素的位置。过程结束后,堆中元
素就按从小到大排序好。
HEAP-SORT
for i=A.heap-size-1 downto 1
	temp = A[i]
	A[i] = A[0]
	A[0] = temp
	A.heap-size = A.heap-size-1
	MAX-HEAPIFY(A,0)

完整代码实现:
#include <stdio.h>
#include <malloc.h>
#define PARENT(i) (i>>1)-1
#define LEFT(i) (i<<1)+1
#define RIGHT(i) (i<<1)+2
#define MAXSIZE 7

struct heap
{
    int heapSize;
    int length;
    int *heapArray;
};

void MaxHeapfy(struct heap *h,int i)
{
    int l = LEFT(i);
    int r = RIGHT(i);
    int largest = 0;

    if(l <= h->heapSize-1 &&
        h->heapArray[i] < h->heapArray[l])
    {
        largest = l;
    }
    else
    {
        largest = i;
    }
    if(r <= h->heapSize-1 &&
        h->heapArray[largest] < h->heapArray[r])
    {
        largest = r;
    }

    if(largest != i)
    {
        int temp = h->heapArray[i];
        h->heapArray[i] = h->heapArray[largest];
        h->heapArray[largest] = temp;
        MaxHeapfy(h,largest);
    }
}

void BuildMaxHeap(struct heap *h)
{
    h->heapSize = h->length;
    for(int i=h->length/2+1;i>=0;i--)
    {
        MaxHeapfy(h,i);
    }
}

void HeapSort(struct heap *h)
{
    for(int i=h->heapSize-1;i>=1;i--)
    {
        int temp = h->heapArray[i];
        h->heapArray[i] = h->heapArray[0];
        h->heapArray[0] = temp;
        h->heapSize--;
        MaxHeapfy(h,0);
    }
}

int main()
{
    int *a = (int *)malloc(sizeof(int)*MAXSIZE);
    struct heap *h = (struct heap *)malloc(sizeof(struct heap));
    h->heapArray = a;
    h->length = MAXSIZE;
    h->heapSize = 0;
    srand(time(0));
    for(int i=0;i<MAXSIZE;i++)
    {
        h->heapArray[i] = rand()%200;
    }

    print_r(h->heapArray,MAXSIZE);
    printf("\n");
    BuildMaxHeap(h);
    HeapSort(h);

    print_r(h->heapArray,MAXSIZE);
    free(a);
    free(h);
    return 0;
}












  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值