面试常见问题之二

[基础算法]面试简单算法实现

 

https://github.com/iyjhabc/simple_algorithm

1、快速排序

  选择数组的其中一个元素(一般为第一个)作为分界pivot,用两个游标分别从后往前和从前往后扫描数组。先从后游标开始,当后游标所指的值比pivot小,则与pivot交换,后游标交换后才扫描前游标;当前游标所指值比pivot大,则与pivot交换。一次分组的结果是pivot前面的元素全部比pivot小,后面的全部比pivot大。既然对前后两部分继续调用分组函数即可完成排序。

  下面的程序对上述过程做了优化,交换的时候直接把游标所指的值覆盖到pivot的位置上,覆盖后,原来游标所指的位置作为下一次的pivot位置,准备被下一次调换时被覆盖。当前后两个游标相遇时,此位置就是pivot值在有序数组中的位置了。此优化其实就是利用了pivot的位置进行元素交换,避免了使用多余的空间。

复制代码
int partition(int *p,int begin,int end){
    int ipivot=begin;
    int pivot=p[begin];
    begin++;
    while(begin<=end){
        while(begin<=end && p[end]>=pivot){
            end--;
        }
        if(begin<=end){
            p[ipivot]=p[end];
            ipivot=end;
            end--;//这里不用忘记了
        }
        while(begin<=end && p[begin]<=pivot){
            begin++;
        }
        if(begin<=end){
            p[ipivot]=p[begin];
            ipivot=begin;
            begin++;//这里不用忘记了
        }
    }
    p[ipivot]=pivot;
    return ipivot;
}

void myqs(int *p,int begin,int end){
    if(begin>=end){
        return;
    }
    int ipivot=partition(p,begin,end);
    myqs(p,begin,ipivot-1);
    myqs(p,ipivot+1,end);
}
复制代码

 2、完全二叉树的判断、二叉树的按层遍历

完全二叉树:给二叉树按层编号,如果二叉树的编号与满二叉树的编号一一对应(但节点数比满二叉树少),就称为完全二叉树。通俗来说就是叶子节点都集中在树的左侧

性质:除最底层外,上层是一棵满二叉树。不可能单独出现右叶节点。利用队列可按层遍历二叉树,遍历过程中如发现叶节点或只有左儿子的节点,则后面的节点都只能为叶子节点,否则不是完全二叉树。如发现节点只有右儿子,则不是完全二叉树。

按层遍历二叉树:从根节点开始,压根节点进队。当队列非空,把队头节点出队(浏览节点数据),把节点左右儿子入队。不断重复此操作至队列为空。

复制代码
void Tree::show_tree_bylevel(){
    queue<BinaryTreeNode*> que;
    que.push(m_pRoot);
    while(!que.empty()){
        cout<<que.front()->m_nValue<<endl;
        if(que.front()->m_pLeft!=NULL)que.push(que.front()->m_pLeft);
        if(que.front()->m_pRight!=NULL)que.push(que.front()->m_pRight);
        que.pop();
    }
}
int Tree::is_complete_binarytree(){
    queue<BinaryTreeNode*> que;
    int flag=0;
    que.push(m_pRoot);
    while(!que.empty()){
        BinaryTreeNode *p=que.front();
        if(flag && (p->m_pLeft!=NULL || p->m_pRight!=NULL))return 0;//标记后只能出现叶子节点
        if(p->m_pLeft==NULL && p->m_pRight!=NULL)return 0;//有单右儿子,不是完全树
        if(p->m_pRight==NULL)flag=1;//出现单左或者叶子节点,则标记
        if(p->m_pLeft!=NULL)que.push(p->m_pLeft);
        if(p->m_pRight!=NULL)que.push(p->m_pRight);
        que.pop();
    }
    return 1;
}
复制代码

 3、普通插入排序与希尔排序

插入排序(insert sort):从数组头往后扫描,发现非升序(降序)的元素时,先记录于buf,把前面的元素往后移动,直到遇到比buf小的元素,则把buf放到此元素后面。复杂度O(n^2)

希尔排序(shell sort):是插入排序的改进。与插入排序不同的是,它把原数组按一定的gap分组,在分组内进行插入排序。逐渐把分组的间隔缩小,最后gap=1时就相当于进行普通的插入排序。因为组内的元素间隔为gap,所以元素需要移动时可以比普通插入排序(即gap=1)时更快地移动,从而提高效率.

4,3,6,2,65,1,7,8
以gap=4: 4 65;3 1;6 7;2 8 
因此1后移到3的位置,其余不需要移动,结果为:4,1,6,2,65,3,7,8
gap=2:4 6 65 7;1 2 3 8
因此7后移到65之后,第二个分组不需移动,结果为:4,1,6,2,7,3,65,8
gap=1:普通的插入排序,把后面的元素往前面的有序部分插入
复制代码
void insert_sort(int *p,int n){
    for(int i=1;i<n;++i){
        if(p[i-1]>p[i]){
            int buf=p[i];//暂存需要插入前方的数据
            int j;
            for(j=i-1;p[j]>buf && j>=0;--j)//数据往后移,直到buf的合适位置
                p[j+1]=p[j];
            p[j+1]=buf;//最后一次操作j多减了1
        }
    }
}
void shell_sort(int *p,int n){
    for(int gap=n/2;gap>0;gap/=2){//最后一次gap=1,以一个普通的插入排序结束
        for(int i=gap;i<n;i++){
            if(p[i-gap]>p[i]){
                int buf=p[i];//暂存需要插入前方的数据
                int j;
                for(j=i-gap;p[j]>buf && j>=0;j-=gap)//数据以gap速度移动,较插入排序快
                    p[j+gap]=p[j];
                p[j+gap]=buf;//最后一次操作j多减了1
            }
        }
    }
}
复制代码

 4、实现strstr函数

复制代码
char* strstr(char *str1,const char *str2){//查找str2,如有则从受托人str2位置开始返回
    /*string target=str1;
    string::size_type k=target.find(str2);
    return str1+k;*/
    size_t len=strlen(str2);
    while(*str1!='\0'){
        if(strncmp(str1,str2,len)==0){//有n不对比到\0,对比len个
            return str1;
        }
        str1++;
    }
    return NULL;
}
复制代码

 5、一句话判断x是否为2的若干次幂

int f(int x){//由于要求一句话,很多地方没考虑,例如输入0也会返回1
    return (x&(x-1))?0:1;
}

 6、辗转相处求最大公约数

如余数为0则被除数为最大公约数;否则以被除数除以余数进行递归。

复制代码
int max_gys(int a,int b){
    /*while(true){//非递归
        if(a%b==0)return b;
        int tmp=a%b;
        a=b;
        b=tmp;
    }
    return 0;*/
    if(a%b==0)return b;
    else return max_gys(b,a%b);
}
复制代码

 6、后缀式(逆波兰式)求四则混合运算

表达式转化为后缀式:1、若数字则直接输出;2、若左括号和优先级比栈顶符号更高(不含相同优先级)的符号,则直接进栈;3、若右括号,则输出栈中左括号以上的;4、若优先级不高于(小于等于)栈顶,则输出直到遇到优先级更高的符号(括号的优先级算最低,比加减乘除低)为止。

复制代码
int is_higher(char a,char b){//*/优先级高于+-,括号优先级低于所有符号
    if((a=='*' || a=='/') && (b=='+' || b=='-'))return 1;
    else if(b=='(' || b==')')return 1;
    else return 0;
}
/*普通中缀表达式转换为后缀表达式*/
void backword(char *mid,char *back){
    stack<char> sign;
    while(*mid!='\0'){
        if(*mid>='0' && *mid<='9')*back++=*mid++;//数字
        else if(*mid==')'){//右括号
            while(sign.top()!='('){
                *back++=sign.top();
                sign.pop();
            }
            sign.pop();
            mid++;
        }else if(*mid=='('){//左括号
            sign.push(*mid++);
        }else if(!sign.empty() && is_higher(*mid,sign.top())){//优先级比栈顶高或左括号
            sign.push(*mid++);
        }else{//优先级不高于
            while(!sign.empty() && !is_higher(*mid,sign.top())){
                *back++=sign.top();
                sign.pop();
            }
            sign.push(*mid++);
        }
    }
    while(!sign.empty()){
        *back++=sign.top();
        sign.pop();
    }
    *back='\0';
}
复制代码

计算后缀式:遇到符号则出栈两个数字计算符号,结果进栈。

复制代码
/*计算后缀式*/
int get_result(char *back){
    stack<int> num;
    while(*back!='\0'){
        if(*back>='0' && *back<='9'){
            num.push(*back-'0');
        }else{
            int right=num.top();
            num.pop();
            int left=num.top();
            num.pop();
            if(*back=='+')
                num.push(left+right);
            else if(*back=='-')
                num.push(left-right);
            else if(*back=='*')
                num.push(left*right);
            else if(*back=='/')
                num.push(left/right);
        }
        back++;
    }
    return num.top();
}
复制代码

 7、按位逆序一个32位长的整数,如110000.。。变为0000.。。11

复制代码
//位操作
void bit_set(int &b,int n){
    b|=(1<<n);
}

void bit_clear(int &b,int n){
    b&=~(1<<n);
}

int bit_check(int &b,int n){
    return b&(1<<n)?1:0;
}

void bit_reverse(int &b,int n){
    b^=(1<<n);
}
int main()
{
    //逆序32位长整数
    int b=0;
    bit_set(b,31);
    bit_set(b,30);
    cout<<hex<<b<<endl;

    int r=0;
    for(int i=31;i>=0;--i){
        if(bit_check(b,i))
            bit_set(r,31-i);
    }
    cout<<hex<<r<<endl;
    return 0;
}
复制代码

 8、倒水问题:给两个容量不同的容器,容器之间可以相互倒水,求能否最终量出容量为n的水

复制代码
//问题的实质是,最终要求的容量能否整除两容器的最小公因数
int gdc(int a,int b){//使用辗转相除法求最小公因数
    if(b>a){
        a^=b;
        b^=a;
        a^=b;
    }
    while(b!=0){
        a=a%b;
        if(b>a){
            a^=b;
            b^=a;
            a^=b;
        }
    }
    return a;
}

bool can(int a,int b,int c){
    int g=gdc(a,b);
    if(c%g==0)
        return true;
    else
        return false;
}
复制代码

 9、二分查找

复制代码
int binary_search_july(int array[],int n,int value)
{
    int left=0;
    int right=n-1;
    //如果这里是int right = n 的话,那么下面有两处地方需要修改,以保证一一对应:
    //1、下面循环的条件则是while(left < right)
    //2、循环内当array[middle]>value 的时候,right = mid
    while (left<=right)          //循环条件,适时而变
    {
        int middle=left + ((right-left)>>1);  //防止溢出,移位也更高效。同时,每次循环都需要更新。

        if (array[middle]>value)
        {
            right =middle-1;   //right赋值,适时而变
        }
        else if(array[middle]<value)
        {
            left=middle+1;
        }
        else
            return middle;
        //可能会有读者认为刚开始时就要判断相等,但毕竟数组中不相等的情况更多
        //如果每次循环都判断一下是否相等,将耗费时间
    }
    return -1;
}

int july_bsearch_recur(int *p,int left,int right,int v){
    if(right<left){
        return -1;
    }
    int mid=left+((right-left)>>1);//使用减法不会溢出,移位效率高
    if(p[mid]<v){
        return july_bsearch_recur(p,mid+1,right,v);//递归时注意参数完整
    }else if(p[mid]>v){
        return july_bsearch_recur(p,left,mid-1,v);
    }else
        return mid;
}
复制代码

二分查找需要注意的坑还是挺多的

1、不要改变p,因此需要定义一个begin和end确定二分的范围,一旦改变了p,最后的返回值将是错误的。

2、mid=left+((right-left)>>1);//使用减法不会溢出,移位效率高

3、编程过程中一定要区分相对数组头p的偏移量(返回值),和相对于begin的偏移量,绝对不能混淆

4、使用right<left这种形式判断结束标志,可以防止左右越界

 

10、约瑟夫环,有1到n个人排成一圈,从第k个人开始从1开始数到m,数到m的人出列,直到没人,求出列顺序

复制代码
void yuesefu(int n,int k,int m){
    list<int> v;
    int i;
    for(i=1;i<=n;++i){
        v.push_back(i);
    }
    list<int>::iterator it=v.begin();

    for(i=0;i<k-1;++i){
        it++;
        if(it==v.end()){
            it=v.begin();
        }
    }


    while(n>0){
        int j=1;
        while(j<=m){
            if(it==v.end()){
                it=v.begin();
            }
            if(j==m){
                cout<<*it<<" ";
                it=v.erase(it);
                n--;
                break;
            }
            it++;
            j++;
        }
    }
    cout<<endl;
}
复制代码

1、使用链表存放一个从1到n  2、把迭代器移到k的位置 3、循环移动,出列

注意,当it到达v.end()的之后,要令it=v.begin()达到循环目的

使用循环链表

复制代码
void ysf(int n,int k,int m){
    node *buf=new node[n];
    int i;
    for(i=1;i<=n;++i){//构造循环链表
        buf[i-1].val=i;
        if(i!=n)
            buf[i-1].next=&buf[i];
        else
            buf[i-1].next=&buf[0];
    }
    node *p=&buf[k-1],*pre;

    while(p->next!=p){//使用这个判断,最后一个不会在循环内输出
        for(i=0;i<m-1;++i){
            pre=p;
            p=p->next;
        }
        cout<<p->val<<" ";
        pre->next=p->next;
        p=p->next;
    }
    cout<<p->val<<" ";//记得 最后一个还没输出!
    cout<<endl;
    delete []buf;
}
复制代码

 11、链地址法hash

本例子的哈希函数只选用简单的求模,遇到冲突时使用链地址法。注意查找时要先检查链上的key有无和当前插入的key一样的,有的话直接修改value,不需插入节点。

复制代码
class hash
{
public:
   link* table[10];
   void put(int key,int val);
   int get(int key);
   hash(){
        memset(table,0,10*sizeof(link*));
   }
};

void hash::put(int key,int val){
    int i=key%10;
    link *p=table[i];
    if(!p){
        table[i]=new link;
        table[i]->key=key;
        table[i]->value=val;
        table[i]->pnext=NULL;
        return;
    }
    while(p){
        if(p->key==key){//检查是否已存在key
            p->value=val;
            return;
        }
        if(p->pnext==NULL){
            p->pnext=new link;
            p->pnext->key=key;
            p->pnext->value=val;
            p->pnext->pnext=NULL;
        }
        p=p->pnext;
    }

}

int hash::get(int key){
    int i=key%10;
    link *p=table[i];
    while(p){
        if(p->key==key){//检查是否已存在key
            return p->value;
        }
        p=p->pnext;
    }
    return -1;
}
复制代码

 

posted @ 2014-04-08 19:24  蒙蒙LOVE  阅读(148)  评论(0编辑  收藏  举报