zoukankan      html  css  js  c++  java
  • 面试常见问题之二

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

     

    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!=''){
            if(strncmp(str1,str2,len)==0){//有n不对比到,对比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!=''){
            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='';
    }
    复制代码

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

    复制代码
    /*计算后缀式*/
    int get_result(char *back){
        stack<int> num;
        while(*back!=''){
            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;
    }
    复制代码
  • 相关阅读:
    Linux查看硬盘使用情况
    2020/05/23,帮亲不帮理
    写作,阅读新单词
    fpga与asic的区别
    ASIC NP FPGA CPU有啥区别?
    基因编程时代要来临了?什么物种都可以创造?细思极恐
    视网膜识别VS虹膜识别 谁更胜一筹
    CNN进化史
    生物神经元与人工神经元模型
    tensorflow简介以及与Keras的关系
  • 原文地址:https://www.cnblogs.com/dmlove/p/3652707.html
Copyright © 2011-2022 走看看