算法汇总



一、二分查找

/*****************************************************
*  二分查找
*      在[low,high)中查找等于value的元素
*      若找到,返回其下标;若没有等于value的元素则返回-1
******************************************************/
#include <stack>
#include <queue>
#include <stdio.h>
using namespace std;

int a[] = { 1, 1, 2, 2, 4, 5 };

int BinarySearch(int value, int low, int high)
{
	while (low <= high){
		int mid = low + (high - low) / 2;//闭区间
		if (a[mid] == value)return mid;
		if (a[mid] > value)high = mid - 1;
		else low = mid + 1;
	}
	return -1;
}

int main()
{
	printf("%d\n", BinarySearch(1, 0, 5));
	printf("%d\n", BinarySearch(5, 0, 5));
	return 0;
}
/*
1
5
*/
分析

A[m] == v:找到,返回

A[m] > v: 所求位置不可能在在m处以及m后面,因此区间变成[x,m)

A[m] < v: 所求位置不可能在在m处以及m前面,因此区间变成[m + 1,y)

/***************************************************************************
*  二分查找
*     在[low,high)中查找等于value的元素
*     若存在于value相等的元素,返回下标指向其中的第一个元素;
*     若没有等于value的元素,则返回“假设这样的元素存在时应该出现的位置”
*     即返回一个迭代器,指向第一个“不小于value”的元素”
****************************************************************************/
int lower_bound(int*a, int value, int low, int high)
{
	while (low < high){
		int mid = low + (high - low) / 2;//闭区间
		if (a[mid] >= value)high = mid;
		else low = mid + 1;
	}
	return low;
}

int main()
{
	int a[] = { 1, 1, 1, 2, 3, 5, 5, 6 };
	printf("%d\n", lower_bound(a, 1, 0, 7));
	printf("%d\n", lower_bound(a, 4, 0, 7));
	return 0;
}
/*
0
5
*/

分析:

首先,最后的返回值不仅可能是x, x+1, x+2,…, y-1,还可能是y——如果v大于A[y-1],就只能插入这里了。 这样,尽管查找区间是左闭右开区间[x,y)返回值的候选区间却是闭区间[x,y]

A[m]=v:至少已经找到一个,而左边可能还有,因此区间变为[x,m]。

A[m] > v: 所求位置不可能在m后面,但可能在m处(因为虽然A[m]!=v,但A[m-1]可能<v,此时要在A[m]处插入v),因此区间变成[x,m]

A[m] < v: 所求位置不可能在在m处以及m前面,因此区间变成[m + 1,y]

这里有一个潜在的危险:如果[x,m]或者[m+1,y]和原区间[x,y]相同,将发生死循环。注意这是不可能的,因为如果[x,m]与【x,y】相同,则m==x,或m + 1 == x,会退出循环;而的下一步y应该=m,此时又退出循环了。


//2.求最大的i,使得a[i] = key,若不存在,则返回-1
int bsearch(int*a, int value, int low, int high)
{
	while (low < high){
		//cout << low << "," << high << endl;
		int mid = low + (high - low + 1) / 2;//闭区间,向上取整,否则会无限循环
		if (a[mid] <= value)low = mid;
		else high = mid - 1;
	}
	if(a[l]==value)return low;
        else return -1;
}
假设1,1,1,3....中查找1

当l=2,r=3时,mid=2,下一步l=mid=2,陷入无限循环;此时应使mid向上取整,才能跳出循环


/***************************************************************************
*  二分查找
*     在[low,high]中查找等于value的元素
*     若存在于value相等的元素,返回下标指向value的下一位置;
*     若没有等于value的元素,则返回“假设这样的元素存在时应该出现的位置”
*     即返回一个迭代器,指向第一个“可插入value的最后一个合适位置“
****************************************************************************/
int upper_bound(int*a, int value, int low, int high)
{
	while (low < high){
		int mid = low + (high - low) / 2;
		if (a[mid] <= value)low = mid + 1;
		else high = mid;
	}
	return low;
}

int main()
{
	int a[] = { 1, 1, 1, 2, 3, 5, 5, 6 };
	printf("%d\n", upper_bound(a, 1, 0, 7));
	printf("%d\n", upper_bound(a, 4, 0, 7));
	return 0;
}
/*
3
5
*/

所查找的区间应该是[y,....

A[m]=v:至少已经找到一个,所求位置应该在m后面,因此区间变为[m + 1,y)。

A[m] < v: 所求位置可能在在m处以及m后面,因此区间变成[m + 1,y)。

A[m] > v: 所求位置不可能在m后面,但可能在m处,但可能在m处(因为虽然A[m]!=v,但A[m-1]可能<v,此时要在A[m]处插入v),因此区间变成[x,m)


二、next_permutation

1、当集合A={1,2,3,4,5。。。}中没有重复元素时

void next_permutation(int n, int* A, int cur) {
    if(cur == n) { //递归边界
        for(int i = 0; i < n; i++) printf("%d ", A[i]);
            printf("\n");
    }
    else for(int i = 1; i <= n; i++) { //尝试在A[cur]中填各种整数i
        int ok = 1;
        for(int j = 0; j < cur; j++)
            if(A[j] == i) ok = 0; //如果i已经在A[0]~A[cur-1]出现过,则不能再选
            if(ok) {
                A[cur] = i;
                next_permutation(n, A, cur+1); //递归调用
        }
    }
}


当A中有重复元素时,上述方法错误

因为

if(A[j] == i) ok = 0; //如果i已经在A[0]~A[cur-1]出现过,则不能再选

这样禁止A数组中出现重复,而在P中本来就有重复元素时,这个禁令是错误的

     解决方法是统计A[0]~A[cur-1]中P[i]的出现次数c1,以及P数组中P[i]的出现次数c2。 只要c1<c2,就能递归调用。

void next_permutation(int n,int P[],int A[],int cur)        //P按顺序存储待排列数,A同样大小的临时空间数组
{
    int i,j;
    if(cur==n){
        for(i=0;i<n;i++) printf("%d ",A[i]);                    //可输出可统计
        printf("\n");
    }
    else{
        for(i=0;i<n;i++){
            int c1=0,c2=0;
            for(j=0;j<cur;j++) if(A[j]==P[i])c1++;
            for(j=0;j<n;j++) if(P[i]==P[j])c2++;
            if(c1<c2){
                A[cur]=P[i];
                next_permutation(n,P,A,cur+1);
            }
        }
    }
}


但此时输入1 1 1,输出了27个1 1 1。遗漏没有了,但是出现了重复:先试着把第1个1作为开头,递归调用结束后再尝试用第2个1

作为开头,递归调用结束后再尝试用第3个1作为开头,再一次递归调用。 可实际上这3个1是相同的,应只递归1次,而不是3次。

      我们枚举的下标i应不重复、 不遗漏地取遍所有P[i]值。 由于P数组已经排过序,所以只需检查P的第一个元素和所有“与前一个

元素不相同”的元素,即只需在“for(i = 0; i< n;i++)”和其后的花括号之前加上“if(!i || P[i] != P[i-1])”即可。

void next_permutation(int n,int P[],int A[],int cur)        //P按顺序存储待排列数,A同样大小的临时空间数组
{
    int i,j;
    if(cur==n){
            for(i=0;i<n;i++) printf("%d ",A[i]);                    //可输出可统计
            printf("\n");
    }
    else{
        for(i=0;i<n;i++){
            if(!i||P[i]!=P[i-1]){
                int c1=0,c2=0;
                for(j=0;j<cur;j++) if(A[j]==P[i])c1++;
                for(j=0;j<n;j++) if(P[i]==P[j])c2++;
                if(c1<c2){
                    A[cur]=P[i];
                    next_permutation(n,P,A,cur+1);
                }
            }
        }
    }
}

三、排序


1、归并排序

     将待排序序列R[0...n-1]看成是n个长度为1的有序序列,将相邻的有序表成对归并,得到n/2个长度为2的有序表;将这些有序序列再次归并,得到n/4个长度为4的有序序列;如此反复进行下去,最后得到一个长度为n的有序序列。

步骤:
     (1)“分解”——将序列每次折半划分。
     (2)“合并”——将划分后的序列段两两合并后排序。
  

       在每次合并过程中,都是对两个有序的序列段进行合并,然后排序。这两个有序序列段分别为 a[low, mid] 和 a[mid+1, high]。先将他们合并到一个局部的暂存数组tmp中,带合并完成后再将tmp复制回a中。为了方便描述,我们称 a[low, mid] 第一段,a[mid+1, high] 为第二段。每次从两个段中取出一个记录进行关键字的比较,将较小者放入tmp中。最后将各段中余下的部分直接复制到tmp中。经过这样的过程,tmp已经是一个有序的序列,再将其复制回a中,一次合并排序就完成了。


示例代码

void merge(int l,int r,int mid)
{
    int pos=0;
    int i=l,j=mid+1;
    while(i<=mid&&j<=r){
        if(a[i]>a[j]){
            tmp[pos++]=a[j++];
        }
        else{
            tmp[pos++]=a[i++];
        }
    }
    while(i<=mid)tmp[pos++]=a[i++];
    while(j<=r)tmp[pos++]=a[j++];
    for(i=0;i<pos;i++)a[l+i]=tmp[i];
}

void merge_sort(int l, int r)
{
    if(l==r)return;
    int mid=(l+r)/2;
    merge_sort(l,mid);
    merge_sort(mid+1,r);
    merge(l,r,mid);
}

归并排序有以下几点优化方法:


1、和快速排序一样,对于小数组可以使用插入排序或者选择排序,避免递归调用。
2、在merge()调用之前,可以判断一下a[mid]是否小于等于a[mid+1]。如果是的话那么就不用归并了,数组已经是有序的。原因很简单,既然两个子数组已经有序了,那么a[mid]是第一个子数组的最大值,a[mid+1]是第二个子数组的最小值。当a[mid]<=a[mid+1]时,数组整体有序。
3、为了节省将元素复制到辅助数组作用的时间,可以在递归调用的每个层次交换原始数组与辅助数组的角色。
4、在merge()方法中的归并过程需要判断i和j是否已经越界,即某半边已经用尽。可以用另一种方式,去掉检测是否某半边已经用尽的代码。具体步骤是将数组a[]的后半部分以降序的方式复制到aux[],然后从两端归并。对于数组{1,2,3}和{2,3,5},第一个子数组照常复制,第二个则从后往前复制,最终aux[]中的元素为{1,2,3,5,3,2}。这种方法的缺点是使得归并排序变为不稳定排序。代码实现如下:

void merge(int[] a, int lo, int mid, int hi, int[] aux) {
for (int k = lo; k <= mid; k++) {
    aux[k] = a[k];
}
for (int k = mid + 1;k <= hi; k++) {
    aux[k] = a[hi - k + mid + 1];
}
int i = lo, j = hi;      //从两端往中间
for (int k = lo; k <= hi; k++)
    if (aux[i] <= aux[j]) a[k] = aux[i++];
    else a[k] = aux[j--];
}


2、插入排序

(1)直接插入排序(Straight Insertion Sort)


       将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。


    要点:设立哨兵,作为临时存储和判断数组边界之用。


      如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺

序, 所以插入排序是稳定的


示例代码

void insert_sort(int a[],int n)
{
    int tmp,j;
    for(int i=1;i<n;i++){
        if(a[i-1]>a[i]){//如果a[i-1]>=a[i]本身就是顺序的,不需要进行排序
            tmp=a[i];
            for(j=i-1;j>=0&&a[j]>tmp;j--){//到a[j]<=tmp停止,tmp应该插到j后面
                a[j+1]=a[j];
            }
            a[j+1]=tmp;
        }
        for(int k=0;k<n-1;k++)printf("%d ",a[k]);printf("%d\n",a[n-1]);
    }
}


2、交换排序

(1)冒泡(Straight Insertion Sort)

基本思想:
      在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大 的数往下沉 ,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。


冒泡排序的示例:

void bubbleSort(int *a,int n)
{
    for(int i=0;i<7-1;i++){
        for(int j=0;j<n-i-1;j++){
            if(a[j]>a[j+1]){
                int tmp=a[j];
                a[j]=a[j+1];
                a[j+1]=tmp;
            }
        }
        print(a,7);
    }
}


冒泡算法的改进:

      (1) 对冒泡排序常见的改进方法是加入一标志性变量exchange,用于标志某一趟排序过程中是否有数据交换,如果进行某一趟排序时并没有进行数据交换,则说明数据已经按要求排列好,可立即结束排序,避免不必要的比较过程。

      (2)对方法1的改进:设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可。

void bubbleSort(int *a,int n)
{
    for(int i=n-1;i>0;){//最开始可以交换的位置为n-1处
        int pos=0;//每趟开始时,没有记录交换
        for(int j=0;j<i;j++){
            if(a[j]>a[j+1]){
                pos=j;
                int tmp=a[j];
                a[j]=a[j+1];
                a[j+1]=tmp;
            }
        }
        i=pos;//若有记录交换,则下次比较到最后一次交换的位置;否则(j<0)可以直接结束比较了
        print(a,7);
    }
}

(2)快速排序(Quick Sort)

基本思想:

      1)选择一个基准元素,通常选择第一个元素或者最后一个元素,

       2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。

       3)此时基准元素在其排好序后的正确位置

       4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。

      (a)一趟排序的过程:


(b)排序的全过程


void swap(int *a,int *b)
{
    int tmp=*a;
    *a=*b;
    *b=tmp;
}
int partion(int *a,int low,int high)
{
    int key=a[low];
    while(low<high){
        while(low<high&&a[high]>=key)high--;
        swap(&a[low],&a[high]);
        while(low<high&&a[low]<=key)low++;
        swap(&a[low],&a[high]);
    }
    print(a,7);
    return low;
}

void quickSort(int *a, int low, int high)
{
    if(low<high){
        int pos=partion(a,low,high);
        quickSort(a,low,pos-1);
        quickSort(a,pos+1,high);
    }
}

3、选择排序

(1)简单选择排序

基本思想:


     在要排序的一组数中,选出最小(或者最大)的 一 个数与第1个位置的数交换; 然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后 一 个数)比较为止。

     算法步骤:
     1、从n 个记录中找出关键码最小的记录与第一个记录交换;
     2、,从第二个记录开始的n-1 个记录中再选出关键码最小的记录与第二个记录交换;
      ..... 
     n,则从第i 个记录开始的n-i+1 个记录中选出关键码最小的记录与第i 个记录交换,
     直到整个序列按关键码有序。


void SelectSort(int *a,int n)
{
    int pos;
    for(int i=0;i<n;i++){
        pos=i;
        for(int j=i+1;j<n;j++){
            if(a[pos]>a[j])pos=j;//最小元素的坐标
        }
        if(pos!=i){//交换
            int tmp=a[pos];
            a[pos]=a[i];
            a[i]=tmp;
        }
        print(a,8);
    }
}

4. 选择排序—堆排序(Heap Sort)

堆排序是一种树形选择排序,是对直接选择排序的有效改进。 

基本思想:

堆的定义如下:具有n个元素的序列(k1,k2,...,kn),当且仅当满足


时称之为堆。由堆的定义可以看出, 堆顶元素 (即第一个元素)必为最小项(小顶堆)。

若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非叶结点的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的。如:

(a)大顶堆序列:(96, 83,27,38,11,09)

(b)  小顶堆序列:(12,36,24,85,47,30,53,91)


初始时把要排序的n个数的序列看作是一棵 顺序存储的二叉树(一维数组存储二叉树) ,调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为 堆排序 。

因此,实现堆排序需解决两个问题:

1. 如何将n 个待排序的数建成堆;

2. 输出堆顶元素后,怎样调整剩余n-1 个元素,使其成为一个新堆。

首先讨论第二个问题:输出堆顶元素后,对剩余n-1元素重新建成堆的调整过程。

调整小顶堆的方法:

1)设有m 个元素的堆,输出堆顶元素后,剩下m-1 个元素。将堆底元素送入堆顶((最后一个元素与堆顶进行交换),堆被破坏,其原因仅是根结点不满足堆的性质。

2)将根结点与左、右子树中较小元素的进行交换。

3)若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复方法 (2).

4)若与右子树交换,如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法 (2).

5)继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。

称这个自根结点到叶子结点的调整过程为筛选。如图:


再讨论对n 个元素初始建堆的过程。

建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。

1)n 个结点的完全二叉树,则最后一个结点是第  个结点的子树。

2)筛选从第 个结点为根的子树开始,该子树成为堆。

3)之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。

如图建堆初始过程:无序序列:(49,38,65,97,76,13,27,49)


   

算法的实现:

从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。

#include <stdio.h>
#define left(i) ((i<<1)|1)

void print(int *a, int n)
{
	for (int i = 0; i < n - 1; i++)printf("%d ", a[i]);
	printf("%d\n", a[n - 1]);
}

void MaxHeapAjust(int *a, int s, int n)
{
	int tmp = a[s];
	int child = 2 * s + 1;
	while (child < n){
		if (child + 1 < n&&a[child]<a[child + 1])child++;
		if (a[s]<a[child]){
			a[s] = a[child];
			s = child;
			child = 2 * s + 1;
                        a[s] = tmp;
		}
		else break;
	}

	print(a, n);
}


void swap(int *a, int*b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

void buildMaxHeap(int *a, int n)
{
	for (int i = (n - 1) / 2; i >= 0; i--)MaxHeapAjust(a, i, n);
	printf("初始建堆:");
	print(a, 10);
}


void HeapSort(int *a, int n)
{
	buildMaxHeap(a, n);
	for (int i = n - 1; i > 0; i--){
		swap(&a[0], &a[i]);
		MaxHeapAjust(a, 0, i);
	}
}

int main()
{
	int a[10] = { 3, 1, 5, 7, 2, 4, 9, 6, 10, 8 };//根从1开始
	printf("初始值:");
	print(a, 10);
	HeapSort(a, 10);
	printf("结果:");
	print(a, 10);
	return 0;
}



5. 递归

(1)八皇后问题
           最简单的思路是把问题转化为从64个格子中选一个子集,使得“子集中恰有8个格子,任意两个选出的格子都不在同一行、同一列或同一个对角线上”。这正是子集枚举问题。但是,64个格子的子集有2^64个,太大了。

          第二个思路是把问题转化为“从64个格子中选8个格子”,这是组合生成问题。根据组合数学,有种方案,但仍不够好。(如何判断不在同一行同一列)。

         在编写递归枚举程序之前,需要深入分析问题,对模型精雕细琢,一般还应对结大数的节点有一个粗略的估计,作为冥界模型的重要标准,下面给出4皇后问题的解答树,它只有17个节点,比4!=24小,这是因为有些节点无法继续扩展。例如在(0,2,*,*)中,第2行无论将皇后放到哪里,都会和第0行和第1行已经放好的皇后发送冲突


void search(int cur)
{
     if(cur == n)ans++;
     else for(int i = 0; i < n; i++){
          C[cur] = i;
          ok = 1;
          for(int j = 0; j < cur; j++){
                if(C[cur] == C[j] || C[cur] - cur == C[j] - j || C[cur] + cur == C[j] + j  ){
                       ok = 0;
                       break;
                 }
          }
          if(ok)Search(cur + 1);
     }
}




1、洗牌算法(随机问题)

方法一

 a)从还没处理的数组(假如还剩k个)中,随机产生一个[0, k]之间的数字p(假设数组从0开始);

 b)从剩下的k个数中把第p个数取出;

 c) 重复步骤2和3直到数字全部取完;

 d) 从步骤3取出的数字序列便是一个打乱了的数列。

点击打开链接


方法二

 a)首先生成一个数组,大小为54,初始化为1~54
 b)按照索引1到54,逐步对每一张索引牌进行洗牌,首先生成一个余数 value = rand %54,那么我们的索引牌就和这个余数牌进行交换处理
 c)等多索引到54结束后,一副牌就洗好了

注:水塘抽样问题

全局、局部洗牌算法

时间复杂度O(n)

空间复杂度为O(0)

(2)随机数生成


(3)随机数函数a,产生0的概率0.7,1的概率0.3。怎样用该随机函数生成一个随机函数b,使得b产生0和1的概率为0.5 


// a()的产生很简单
int a(){
	return (rand() % 10) < 7;
}
// b()需要利用a()实现,较复杂
int b(){
	int sum = 0;
	for (int i = 0; i<RAND_MAX; ++i) sum += a();
	return (sum < RAND_MAX * 3 / 10);
}
int main()
{

	return 0;
}


2、KMP模板 O(M+N)

next数组含义:代表当前字符之前的字符串中,有多大长度的相同前缀后缀。

#include<stdio.h>
int a[1000010],b[10010];
int next[10010];
int n,m;
void getNext()
{
    int j,k;
    j=0;
    k=-1;
    next[0]=-1;
    while(j<m)
    {
        if(k==-1||b[j]==b[k])
          next[++j]=++k;
        else k=next[k];
    }    
}  
//返回首次出现的位置 
int KMP_Index()
{
    int i=0,j=0;
    getNext();
    
    while(i<n && j<m)
    {
        if(j==-1||a[i]==b[j])
        {
            i++;
            j++;
        }    
        else j=next[j];
        
    }    
    if(j==m) return i-m+1;
    else return -1;
}      
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++)
          scanf("%d",&a[i]);
        for(int i=0;i<m;i++)
          scanf("%d",&b[i]);
        printf("%d\n",KMP_Index());
    }    
    return 0;
}

3、二叉树中序遍历(非递归)

class Solution {
public:
    int getMinimumDifference(TreeNode* root) {
        int Min = INT_MAX, pre = -1;
        stack<TreeNode*>s;
        TreeNode* cur = root;
        while(cur || !s.empty()){
            while(cur){
                s.push(cur);
                 cur = cur->left;
            }
            cur = s.top();
            s.pop();
            if(pre != -1)Min = min(Min, cur->val - pre);
            pre = cur->val;
            cur = cur->right;
        }
        return Min;
    }

private:
};

非递归先序遍历

http://blog.csdn.net/cxllyg/article/details/7520037

非递归后续遍历 


void PostOrder_Nonrecursive1(BiTree T)  // 后序遍历的非递归      
{
	stack<BiTree> s;
	BiTree cur = T;           // 指向当前要检查的节点    
	BiTree pre = NULL;    // 指向前一个被访问的节点    
	while (cur || !s.empty())  // 栈空时结束      
	{
		while (cur)            // 一直向左走直到为空    
		{
			s.push(cur);
			cur = cur->lchild;
		}
		cur = s.top();
		// 当前节点的右孩子如果为空或者已经被访问,则访问当前节点    
		if (cur->rchild == NULL || cur->rchild == pre)//如果已经访问过其右,下个肯定要访问cur节点
		{
			cout << cur->data << "  ";
			pre = cur;
			s.pop();
			cur = NULL;
		}
		else
			cur = cur->rchild;      //还有右孩子,且没有访问, 则访问右孩子    
	}
}



3、strcpy函数

strcpy和memcpy主要有以下3方面的区别。
1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy

http://blog.csdn.net/gpengtao/article/details/7464061/


4、单链表快排

http://blog.csdn.net/otuhacker/article/details/10366563

 ListNode* Partion(ListNode* pBegin, ListNode *pEnd)
 {
	 int key = pBegin->val;
	 ListNode* p = pBegin;
	 ListNode* q = pBegin->next;
	 while (q != pEnd){
		 if (q->val < key){
			 p = p->next;
			 swap(p->val, q->val);
		 }
		 q = q->next;
	 }
	 swap(p->val, pBegin->val);
	 return p;
 }

 ListNode* QuickSort(ListNode* pBegin, ListNode* pEnd)
 {
	 if (pBegin != pEnd){
		 ListNode* pPos = Partion(pBegin, pEnd);
		 QuickSort(pBegin, pPos);
		 QuickSort(pPos->next, pEnd);
	 }
	 return pBegin;

 }

//调用
root = QuickSort(root, NULL);


单链表归并排序

5、大数

   a、阶乘

const int maxn = 50000;
int ans[maxn];
void factorial(int n)
{
	int ind = 1, carry = 0;
	ans[0] = 1;

	for (int i = 2; i <= n; i++){
		for (int j = 0; j < ind; j++){
			ans[j] = ans[j] * i + carry;
			carry = ans[j] / 10;
			ans[j] = ans[j] % 10;
		}
		while (carry){
			ans[ind++] = carry % 10;
			carry = carry / 10;
		}
	}
	for (int i = ind - 1; i >= 0; i--)printf("%d", ans[i]);
	printf("\n");
}


b、BigNum类四则运算

class BigNum
{
public:
	BigNum(){ len = 0; memset(d, 0, sizeof(d)); }
	BigNum(int);
	BigNum(const char*);
	BigNum(const BigNum& b);
	BigNum& BigNum::operator=(const BigNum & T);
	bool operator == (const BigNum&)const;
	bool operator < (const BigNum&)const;
	//bool operator > (const BigNum&);
	BigNum operator + (const BigNum& T) const;
	BigNum operator - (const BigNum& T) const;
	BigNum operator*(const BigNum & T) const;
	BigNum operator / (const const int& b) const;

	void print(){
		for (int i = len - 1; i >= 0; i--){
			printf("%d", d[i]);
		}
		printf("\n");
	}
public:
	int d[maxn];
	int len;

};

BigNum::BigNum(int n)
{
	memset(d, 0, sizeof(d));
	int x = n;
	len = 0;
	while (x){
		d[len++] = x % 10;
		x = x / 10;
	}
}

BigNum::BigNum(const char* s)
{
	memset(d, 0, sizeof(d));
	int l = strlen(s);
	len = 0;
	for (int i = l - 1; i >= 0; i--){
		d[len++] = s[i] - '0';
	}
}

BigNum::BigNum(const BigNum& T) :len(T.len)
{
	for (int i = 0; i < len; i++)d[i] = T.d[i];
}

BigNum& BigNum::operator=(const BigNum & T)   //重载赋值运算符,大数之间进行赋值运算  
{
	int i;
	len = T.len;
	memset(d, 0, sizeof(d));
	for (i = 0; i < len; i++)
		d[i] = T.d[i];
	return *this;
}

BigNum BigNum::operator +(const BigNum& T) const
{
	BigNum c(*this);
	int carry = 0;
	int mlen = max(len, T.len);
	for (int i = 0; i < mlen; i++){
		c.d[i] += T.d[i] + carry;
		carry = c.d[i] / 10;
		c.d[i] = c.d[i] % 10;
	}
	if (carry){
		c.d[c.len] = 1;
		c.len += 1;
	}
	return c;
}

BigNum BigNum::operator -(const BigNum& T)const
{
	bool neg = false;
	BigNum a(*this), b(T);
	if (a < b){
		neg = true;
		BigNum tmp = a;
		a = b;
		b = tmp;
	}
	int mlen = max(len, b.len);
	for (int i = 0; i < mlen; i++){
		if (a.d[i] >= b.d[i])a.d[i] = a.d[i] - b.d[i];
		else {
			a.d[i] = a.d[i] - b.d[i] + 10;
			a.d[i + 1]--;
		}
	}
	while (a.d[a.len - 1] == 0 && a.len > 1)a.len--;
	if (neg)
		a.d[a.len - 1] = -a.d[a.len - 1];
	return a;
}

BigNum BigNum::operator*(const BigNum & T) const{
	BigNum c;
	int i, j, carry;
	int t;
	for (i = 0; i < len; i++){
		carry = 0;
		for (j = 0; j < T.len; j++){
			t = d[i] * T.d[j] + c.d[i + j] + carry;
			c.d[i + j] = t % 10;
			carry = t / 10;
		}
		if (carry)c.d[i + j] = carry;
	}
	c.len = i + j;
	while (c.d[c.len - 1] == 0 && c.len > 1)c.len--;
	return c;
}

BigNum BigNum::operator / (const int& b) const{
	BigNum c;
	int down = 0;
	for (int i = len - 1; i >= 0; i--){
		int tmp = d[i] + down * 10;
		c.d[i] = tmp / b;
		down = tmp % 10;
	}
	c.len = len;
	while (c.d[len - 1] == 0 && c.len > 1)c.len--;
	return c;
}

bool BigNum::operator==(const BigNum& b) const
{
	if (len != b.len)return false;
	for (int i = len - 1; i >= 0; i--){
		if (d[i] != b.d[i])return false;
	}
	return true;
}

bool BigNum::operator <(const BigNum& b) const
{
	if (len < b.len)return true;
	else if (len == b.len){
		int i = len - 1;
		while (d[i] == b.d[i] && i >= 0)i--;
		if (i > 0 && d[i] < b.d[i])return true;
		else return false;
	}
	else return false;
}


6、最长上升子序列
#O(n^2)
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        vector<int>dp(n, 1);
        int res = 0;
        for(int i = 0; i < n; i++){
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i]){
                    dp[i] = max(dp[i], dp[j] + 1);
                }
            }
            res = max(res, dp[i]);
        }
        return res;
    }
};


设d[i]为以i结尾的最长上升子序列的长度,那么它的前一个元素是什么呢?设它
为j,则d[i] = max{d[j]} + 1,其中枚举条件是j < i且a[j] < a[i]。状态有O(n)个,决
策O(n)个,时间复杂度为O(n2)。下面考虑如何把它优化到O(n log n)。
把同一个i对应的d[i]和a[i]看成一个二元组(d, a)。在计算d[i]时,考虑所有满足j <
i的二元组(d, a)。显然它们的i是无关紧要的,只需要找出其中a[j] < a[i]的最大d[j]即
可。换句话说,程序看起来应该是这样的:
其中getmax(S,a[i])表示在集合S中所有a值小于a[i]的二元组中寻找d的最大值。
update(S,a[i],d[i])表示用二元组a[i],d[i]更新集合S。
现在问题的关键是实现集合S。考虑二元组(5, 4)和(5, 3)。它们d值相同但a值不
同。对于后续决策来说, (5, 4)是合法时(5, 3)一定合法,而且(5, 4)和(5, 3)一样好。因
此我们规定:只保留(5, 3)!换句话说: d值相同的a只保留一个。用g[i]表示d值为i的
最小a,那么一定有g[1] ≤ g[2] ≤ ... ≤ g[n]。不存在的d值对应
的g设为正无穷。
d[i]的计算 可以使用二分查找得到大于等于a[i]的第一个数j,则d[i] = j(本来是找
不大于a[i]的最后一个数j,则d[i] = j + 1,但这样转化后更方便)。
g的更新 由于g[j] > a[i],且i的d值为j,因此需要更新g[j] = a[i]。

#nlogn
class Solution {
public:
	int lengthOfLIS(vector<int>& nums) {
		int n = nums.size();
		int res = 0;
		vector<int>dp(n, 1);
		vector<int>g(n, INT_MAX);
		for (int i = 0; i < n; i++){
			int j = lower_bound(g.begin(), g.end(), nums[i]) - g.begin();
			g[j] = nums[i];
			dp[i] = j + 1;
			res = max(res, dp[i]);
		}
		return res;
	}
};

最长公共子序列
长度分别为m和n;
  ·创建1个二维数组dp[m.n];
    ·初始化dp数组内容为0
    ·m和n分别从0开始,m++,n++循环:
       - 如果str1[m] == str2[n],则dp[m,n] = dp[m - 1, n -1] + 1;
       - 如果str1[m] != str2[n],则dp[m,n] = max{dp[m,n - 1],dp[m - 1, n]}
    ·最后从dp[m,n]中的数字一定是最大的,且这个数字就是最长公共子序列的长度
    ·从数组L中找出一个最长的公共子序列
p[i][j]表示0到i-1跟0到j-1的最长公共子序列
#include <iostream>  
using namespace std;  
#define M 5005  
  
int dp[M][M];  
char a[M], b[M];  
  
int main()  
{  
    int i, j, la, lb;  
    while (~scanf ("%s%s", a, b))  
    {  
        la = strlen (a), lb = strlen (b);  
        for (i = 0; i < la; i++) //边界初始化  
            dp[i][0] = 0;  
        for (j = 0; j < lb; j++)  
            dp[0][j] = 0;  
        for (i = 1; i <= la; i++)  
        {  
            for (j = 1; j <= lb; j++)  
            {  
                //状态转移  
                if (a[i-1] == b[j-1])  
                    dp[i][j] = dp[i-1][j-1] + 1;  
                else dp[i][j] = max (dp[i-1][j], dp[i][j-1]);  
            }  
        }  
        printf ("%d\n", dp[la][lb]);  
    }  
    return 0;  
}

7、list自带排序函数的排序原理。

        归并排序变形:http://blog.csdn.net/shoulinjun/article/details/19501811

将前两个元素合并,再将后两个元素合并,然后合并这两个子序列成4个元素的子序列,重复这一过程,得到8个,16个,...,子序列,最后得到的就是排序后的序列。

时间复杂度:O(nlgn)

list的sort实现:merge排序变形

[cpp]  view plain  copy
  1. template <class T, class Alloc>  
  2.   
  3.  void list<T, Alloc>::sort() {  
  4.   
  5.   if (node->next == node || link_type(node->next)->next == node) return;  
  6.   
  7.   list<T, Alloc> carry;  
  8.   
  9.   list<T, Alloc> counter[64];  
  10.   
  11.   int fill = 0;  
  12.   
  13.   while (!empty()) {  
  14.   
  15.     carry.splice(carry.begin(), *this, begin());  
  16.   
  17.     int i = 0;  
  18.   
  19.     while(i < fill && !counter[i].empty()) {  
  20.   
  21.       counter[i].merge(carry);  
  22.   
  23.       carry.swap(counter[i++]);  
  24.   
  25.     }  
  26.   
  27.     carry.swap(counter[i]);          
  28.   
  29.     if (i == fill) ++fill;  
  30.   
  31.   }  
  32.   
  33.   for (int i = 1; i < fill; ++i) counter[i].merge(counter[i-1]);  
  34.   
  35.   swap(counter[fill-1]);  
  36.   
  37. }  

分析

对于序列7,6,5,4,3,2,1

第一趟:

carry.splice(carry.begin(), *this, begin());    ->carry:7

carry.swap(counter[i]);   ->carry:NULL,counter[0]:7

i==fill(0)->fill:1


第二趟:

carry.splice(carry.begin(), *this, begin());->carry:6

i=0,fill=1,count[0]不为empty,进入循环

counter[i].merge(carry);count[0]和carry进行归并    ->carry:NULL,counter[0]:6,7

carry.swap(counter[i++]);   ->carry:6,7

退出U型你换

carry.swap(counter[i]);    ->counter[1]:6,7

第三趟:

carry.splice(carry.begin(), s, s.begin());   ->carry:5

counter[1]:empty   ->跳过循环

carry.swap(counter[i]);   ->counter[0]:5


第四趟:

carry.splice(carry.begin(), s, s.begin());   ->carry:4

i=0,fill=2,进入循环

counter[i].merge(carry);   ->counter[0]:4,5

carry.swap(counter[i++]);   ->carry:4,5

i=1

counter[i].merge(carry);   ->counter[1]:4,5,6,7

carry.swap(counter[i++]);   ->carry:4,5,6,7

退出循环

carry.swap(counter[i]);    ->counter[2]:4,5,6,7

++fill;   ->fill:3


.....


for (int i = 1; i < fill; ++i) counter[i].merge(counter[i-1]);    ->最后来一次总的归并


总结:

     将前两个元素归并,再将后两个元素归并,归并这两个小子序列成为4个元素的有序子序列;重复这一过程,得到8个元素的有序子序列,16个的,32个的。。。,直到全部处理完。主要调用了swap和merge函数,而这些又依赖于内部实现的transfer函数(其时间代价为O(1))。该mergesort算法时间代价亦为n*lg(n),计算起来比较复杂。list_sort中预留了 64个temp_list(counter[64]),所以最多可以处理2^64-1个元素的序列,这应该足够了。








1. 分治法与动态规划主要共同点:
二者都要求原问题具有最优子结构性质,都是将原问题分而治之,分解成若干个规模较小(小到很容易解决的程序)的子问题.然后将子问题的解合并,形成原问题的解.
 
2. 分治法与动态规划实现方法:
① 分治法通常利用递归求解.
② 动态规划通常利用迭代法自底向上求解,但也能用具有记忆功能的递归法自顶向下求解.
 
3. 分治法与动态规划主要区别:
① 分治法将分解后的子问题看成相互独立的.
② 动态规划将分解后的子问题理解为相互间有联系,有重叠部分.
背包问题


10、海量数据

十道海量数据处理面试题与十个方法大总结
从头到尾解析Hash 表算法

教你如何迅速秒杀掉:99%的海量数据处理面试题

BitMap


11、数据结构

BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树

从B树、B+树、B*树谈到R 树

循环队列实现(C++) Ring Buffer


12、博弈

(1)先手获胜

eg.1  在球框里总共有100个球,拿到100个算赢。两个人分别拿,最多单次拿5个,最少1个。规定你先拿,那么第一次拿几个,才能保证你的胜利?

第一次拿4个,
然后别人拿a个,
你就拿6-a个
(a=1,2,3,4,5)
这样的话,你就可以拿到
4,10,16,22,……94,100


eg.2

 2000个球,两个人轮换着抓,每次可以抓的球数量为2,4,8,抓到最后一球的人获胜.让你先抓,你第一次要抓多少,才能保证赢第1次抓2个.
思路:
先抓2个以后,剩下1998个球.
一直8个8个抓,双方要抓249次,抓到最后,轮到他抓的时候,剩下6个球.这6个球他要么抓2个要么抓4个,总会留4个或者2个留在最后给你抓.根据题意,抓最后那一下的算赢,所以先抓2个,必定赢.
计算方式如下:
2000-2=1998.(此时该对方抓)
连续抓249次,每次8个.则有249*8=1992个.剩1998-1992=6个(此时又轮到对方抓).
不管对方怎么抓,都是对方输.


13、不送除法计算a/3

int div3(int num)
{
	return (int)((__int64)num * 0x55555556 >> 32);
}

14、 M进制化成N进制

15、 LRU

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
03-10 751

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值