• 算法


    总结一些相关的算法,并进行了分类。如有错误或更好的解决方法,望能留言指出。

    一、字符串

    1.全排列算法

    思路一:

    (1)n个元素的全排列=(n-1个元素的全排列)+(另一个元素作为前缀);

    (2)出口:如果只有一个元素的全排列,则说明已经排完,则输出数组;

    (3)不断将每个元素放作第一个元素,然后将这个元素作为前缀,并将其余元素继续全排列,等到出口,出口出去后还需要还原数组;

    思路二:全排列就是从第一个数字起每个数分别与它后面的数字交换。

    思路三:

    下面是具体的代码实现:

    //全排列的递归实现
    #include <stdio.h>
    #include <string.h>
    void Swap(char *a, char *b)
    {
        char t = *a;
        *a = *b;
        *b = t;
    }
    //k表示当前选取到第几个数,m为最后一个数的索引.
    void AllRange(char *pszStr, int k, int m)
    {
        if (k == m)
        {
            static int s_i = 1;
            printf("  第%3d个排列	%s
    ", s_i++, pszStr);
        }
        else
        {
            for (int i = k; i <= m; i++) //第i个数分别与它后面的数字交换就能得到新的排列
            {
                Swap(pszStr + k, pszStr + i);
                AllRange(pszStr, k + 1, m);
                Swap(pszStr + k, pszStr + i);
            }
        }
    }
    void Foo(char *pszStr)
    {
        AllRange(pszStr, 0, strlen(pszStr) - 1);
    }
    int main()
    {
        printf("         全排列的递归实现
    ");
        char szTextStr[] = "123";
        printf("%s的全排列如下:
    ", szTextStr);
        Foo(szTextStr);
        return 0;
    }

    但是,我们会发现,这样写以后,如果字符串中有重复的元素,则全排列也会有重复的,所以我们需要加个去重的处理:(AllRange方法中)

    交换之前判断是否相同,相同则不作交换。

    完善后的代码:

    //去重全排列的递归实现
    #include <stdio.h>
    #include <string.h>
    void Swap(char *a, char *b)
    {
        char t = *a;
        *a = *b;
        *b = t;
    }
    //在pszStr数组中,[nBegin,nEnd)中是否有数字与下标为nEnd的数字相等
    bool IsSwap(char *pszStr, int nBegin, int nEnd)
    {
        for (int i = nBegin; i < nEnd; i++)
            if (pszStr[i] == pszStr[nEnd])
                return false;
        return true;
    }
    //k表示当前选取到第几个数,m为最后一个数的作引
    void AllRange(char *pszStr, int k, int m)
    {
        if (k == m)
        {
            static int s_i = 1;
            printf("  第%3d个排列	%s
    ", s_i++, pszStr);
        }
        else
        {
            for (int i = k; i <= m; i++) //第i个数分别与它后面的数字交换就能得到新的排列
            {
                if (IsSwap(pszStr, k, i))
                {
                    Swap(pszStr + k, pszStr + i);
                    AllRange(pszStr, k + 1, m);
                    Swap(pszStr + k, pszStr + i);
                }
            }
        }
    }
    void Foo(char *pszStr)
    {
        AllRange(pszStr, 0, strlen(pszStr) - 1);
    }
    int main()
    {
        printf("         去重全排列的递归实现
    ");
    char szTextStr[] = "122";
        printf("%s的全排列如下:
    ", szTextStr);
        Foo(szTextStr);
        return 0;
    }

    2.字符串转int

    整数的溢出问题,MAX表示的是最大值2147483647的16进制表示,MIN表示的是最小负值-2147483648的16进制表示
    #define kIntMAX ((int)0x7FFFFFFF)
    #define kIntMIN ((int)0x80000000)
    
    //字符串转int
    /*
    通过试系统的atoi函数,总结了下规则:
    1.开头只能为数字、正负号、空格(tab制表符、换行、回车)
    2.正负号之后必须为数字,否则退出
    3.数字开始之后必须为数字,出现其他字符则退出。
    错误处理:
    1.NULL或空字符串(""),返回0
    2.溢出则返回-1
     */
    int myatoi(const char * str)
    {
        bool negative=false;//是否为负数
        unsigned long result=0;
        const char *pCurrent = str;
        
        //判空处理
        if ((pCurrent == NULL) || (*pCurrent == '')) {
            return 0;
            printf("_____len");
        }
    
        //
        while (*pCurrent) {
            if (isspace(*pCurrent)) {
                pCurrent++;
            }
            else{
                break;
            }
        }
        
        //处理符号
        while (*pCurrent) {
            if (*pCurrent == '-') {
                negative = true;
                pCurrent++;
                break;
            }
            else if (*pCurrent == '+')
            {
                pCurrent++;
                break;
            }
            else if ((*pCurrent>='0') && (*pCurrent<='9'))
            {
                break;
            }
            else
            {
                return 0;
            }
        }
        
        //处理数字
        while (*pCurrent) {
            if ((*pCurrent>='0') && (*pCurrent<='9')) {
                result = (result*10 + (*pCurrent-'0'));
                pCurrent++;
            }
            else
            {
                break;
            }
        }
        
        //处理溢出
        if ((negative && result>kIntMIN) || (!negative && result>kIntMAX)) {
            return -1;
        }
        
        if (negative) {
            result *= -1;
        }
        return (int)result;
    }

     3.字符串翻转

    //翻转字符串
    /*
     考察点:
     1.指针和字符串的理解
     2.合法性检查
     3.是否有返回值
     */
    char *myReversalStr(char *string)
    {
        char *start = string;
        char *end = string;
        char temp;
        
        if (string != NULL) {
            while (*end++);
            end-=2;
            
            while (start<end) {
                temp = *start;
                *start++ = *end;
                *end-- = temp;
            }
        }
        
        return string;
    }

    4. 字符串中的单词逆序输出

    先每个单词内部逆序,然后再整个字符串逆序。在这里可以以空格符作为每个单词区分的标识符,并注意最后一个单词的逆序问题即可实现。

    //辅助函数
    void reverseString(char *start, char *end)
    {
        while (start<end) {
            char temp = *start;
            *start = *end;
            *end = temp;
            start++;
            end--;
        }
    }
    //单词逆序输出
    void reverseStringWord(char *str)
    {
        if (str==NULL) {
            return;
        }
        
        char *pCurrent = str;
        char *pStart = str;
        bool isHaveSpace = false;
        while (*pCurrent != '') {
            if (*pCurrent == ' ') {
                isHaveSpace = true;
                reverseString(pStart,pCurrent-1);
                pStart = pCurrent+1;
            }
            pCurrent++;
        }
        
        if (isHaveSpace) {
            reverseString(pStart, pCurrent-1);
            reverseString(str, pCurrent-1);
        }
    }

     5.给出一个字符串,其中只包含括号(大中小括号 “()[]{}” ),括号可以任意嵌套。如果同样的左右括号成对出现并且嵌套正确,那么认为它是匹配的。例如:

      ()  ->  TRUE  (匹配)

      [()]  ->  TRUE  (匹配,括号可以嵌套)

      ()()   ->  TRUE     (匹配,括号可以并列排列)

      ({}([]))  ->  TRUE   (匹配,括号可以任意嵌套,大括号不必在外)

      )   ->  FALSE    (不匹配,缺少左括号)

      (}   ->  FALSE     (不匹配,左右括号不一样)

      {)(}   ->  FALSE     (不匹配,左右括号相反)

    思路:采用进站出站的思想,遍历完字符串时如果array为空则匹配成功,否则失败

    int flagTranslateWithChar(char c)
    {
        if (c == '(') {
            return -1;
        }
        else if (c == ')') {
            return 1;
        }
        else if (c == '[') {
            return -2;
        }
        else if (c == ']') {
            return 2;
        }
        else if (c == '{') {
            return -3;
        }
        else if (c == '}') {
            return 3;
        }
        else{
            return 0;
        }
    }
    bool isMatch(char *str)
    {
        if (str == NULL || strlen(str)<=1) {
            return false;
        }
        
        size_t len = strlen(str);
        int array[len];
        int index = -1;
        memset(array, 0, sizeof(array));
        while (*str) {
            int flag = flagTranslateWithChar(*str);
            if (flag==0) {
                str++;
                continue;
            }
            if (index==-1) {
                index++;
                array[index]=flag;
                str++;
                continue;
            }
            
            if (array[index] + flag == 0) {
                array[index] = 0;
                index--;
            }
            else
            {
                array[index+1] = flag;
                index++;
            }
            
            str++;
        }
        
        if (array[0]==0) {
            return true;
        }
        else
        {
            return false;
        }
    }

    二、数组

    1.快速排序法:

    思路:虽然快速排序称为分治法,但分治法这三个字显然无法很好的概括快速排序的全部步骤。比较完整的来说应该是:挖坑填数+分治法

    挖坑填数的说明:L:最左边索引,R:最右边索引

    1.i =L; j = R; 将基准数即最左边第一个数挖出形成第一个坑a[i]。

    while循环{

    2.j--由后向前找比基准数小的数,找到后挖出此数填前一个坑a[i]中。

    3.i++由前向后找比基准数大的数,找到后也挖出此数填到前一个坑a[j]中。

    }

    4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中。

    5.这样整个数据中就被基准数分为了两个区间,左边比它小,右边比它大。

    分治法说明:再通过递归对左右区间重复第二步,直到各区间只有一个数。

    完整的代码如下:

    //快速排序:挖坑填数+分治法
    void fastSort(int *arr, int left, int right)
    {
        if (left<right && arr!=NULL) {
            int i = left;//left、right要保留,最后要用
            int j = right;
            int key = arr[left];
            
            /******挖坑填数******/
            //每个大while循环:对left作为基准值进行了分区,小的放在了左边,大的放在了右边
            while (i<j) {
                while (i<j && arr[j]>=key) {
                    j--;
                }
                arr[i]=arr[j];//拿j(后边)的数填到i(前边)的坑里
                
                while (i<j && arr[i]<=key) {
                    i++;
                }
                arr[j]=arr[i];//拿i(前边)的数填到j(后边)的坑里
            }
            arr[i]=key;
            /******挖坑填数******/
            
            /******分治法******/
            fastSort(arr, left, i-1);
            fastSort(arr, i+1, right);
            /******分治法******/
    
        }
    }

     三、链表

    1.翻转链表

    typedef struct NODE{
        char data;
        struct NODE *next;
    }node;
    
    //创建链表:用一个临时变量标识最后一个节点。每新加一个节点,这个标识的next就指向新增的地址,然后这个标识再往后挪,新增的节点则为新的标识
    node *createLinkList(int len)
    {
        if (len<1) {
            return NULL;
        }
        node *head = (node *)malloc(sizeof(node));
        node *last = head;
        int count = 1;
        while (count<=len) {
            node *newNode = (node *)malloc(sizeof(node));
            newNode->next = NULL;
            
            last->next = newNode;
            last = newNode;
         count++; }
    return head; } //翻转链表 node *reverseLinkList(node *head) { if (head==NULL) { return head; } node *pB,*pF,*temp; pF=head;//前边的指针 pB=NULL;//后边的指针 temp= NULL; while (pF) {//每一步只是把next指针从后指到前边,后边的节点先用temp临时变量保存住 temp = pF->next; pF->next = pB; pB = pF; pF = temp; } return pB; }

     2.合并两个有序的链表

    1.递归

    //该方法head1、head2都不是头,应该是head->next
    node *mergeNodeList(node* head1, node *head2)
    {
        if (head1==NULL) {
            return head2;
        }
        if (head2==NULL) {
            return head1;
        }
        
        node *ret;
        if (head1->next<head2->next) {
            ret = head1;
            ret->next = mergeNodeList(head1->,head2);
        }
        else{
            ret = head2;
            ret->next = mergeNodeList(head1,head2->next);
        }
        
        return ret;
    }

    2.常规方法(非递归)

    node *mergeTwoNodeList(node *head1, node *head2)
    {
        node *ret = NULL;
        
        if (head1->next->data < head2->next->data) {
            ret = head1;
        }
        else{
            ret = head2;
        }
        
        node *currentNode = ret;
        node *head1 = head1->next;
        node *head2 = head2->next;
        
      //遍历两个链表,按顺序放到currentNode后边
    while (head1!=NULL && head2!=NULL) { if (head1->data < head2->data) { currentNode->next = head1; currentNode = head1; head1 = head1->next; } else{ currentNode->next = head2; currentNode = head2; head2 = head2->next; } } if (head1!=NULL) { currentNode->next = head1; } if (head2!=NULL) { currentNode->next = head2; } return ret; }

     3.链表是否有环:用快慢指针法,如果优化会相遇

    int hsaLoopNodeList(node *head)
    {
        if (head == NULL) {
            return 0;
        }
        node *p = head;
        node *q = head;
        while (p!=NULL && q!=NULL && q->next!=NULL) {
            p=p->next;
            q=q->next->next;
            if (p==q) {
                return 1;
            }
        }
        return 0;
    }

     4.判断两链表是否相交

    思路:链表相交,必定为Y或V类型

    两种方法:一:1.遍历两个链表,获取长度差值;2.长的先走插值,再次遍历两个链表,判断是否有相交的点即可。

              二:把其中一个链表首尾相连,如果是相交的,则就成为一个有环链表。

    //方法一:
    node *crossNodeFromList(node *head1, node *head2)
    {
        if (head1 == NULL || head2 == NULL) {
            return NULL;
        }
        node *p=head1;
        node *q=head2;
        
        int len1 = 0;
        int len2 = 0;
        while (p) {
            len1++;
            p=p->next;
        }
        while (q) {
            len2++;
            q=q->next;
        }
        
        p=head1;
        q=head2;
        if (len1>len2) {
            for (int i=0; i<(len1-len2); i++) {
                p=p->next;
            }
        }
        else
        {
            for (int i=0; i<(len2-len1); i++) {
                q=q->next;
            }
        }
        
        while (p!=NULL && q!=NULL) {
            if (p==q) {
                break;
            }
            p=p->next;
            q=q->next;
        }
        if (p==NULL || q==NULL) {//表明没有交叉点
            return NULL;
        }
        return p;
    }

     四、二叉树:

    这个讲的通俗易懂:http://www.jianshu.com/p/c5d48937f2d9

    1.前序遍历二叉树:

    前序遍历首先访问根结点然后遍历左子树,最后遍历右子树

    //前序遍历  
    void preOrder(struct BiTNode *bt)
    {
    if(bt != NULL){
              printf("%c ", bt->data); /* 访问根结点 */
              preOrder(bt->left); /* 前序遍历左子树 */
              preOrder(bt->right); /* 前序遍历右子树 */
           }
              return;
    }

    2.中序遍历二叉树

    中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。

    void inOrder(struct BTreeNode *bt){
       if(bt != NULL){
           inOrder(bt->left); /* 中序遍历左子树 */
            printf("%c ", bt->data); /* 访问根结点 */
           inOrder(bt->right); /* 中序遍历右子树 */
          }
           return;
       }

    3.后序遍历二叉树

    后序遍历首先遍历左子树,然后遍历右子树,最后访问根结点。

    void inOrder(struct BTreeNode *bt){
       if(bt != NULL){
           inOrder(bt->left); /* 后序遍历左子树 */
           inOrder(bt->right); /* 后序遍历右子树 */
            printf("%c ", bt->data); /* 访问根结点 */
          }
           return;
      }

    4.应用:

    二叉树的先序遍历为FBACDEGH,中序遍历为ABDCEFGH,请写出这个二叉树的后序遍历结果: ADECBHGF。

    分析:1.从先序FBACDEGH中,首先可以分析得到,F为根;再回到中序ABDCEFGH,可知ABCDE 在F的左边,GH在右边。

       2.接下来分析ABCDE,根据先序是BACDE,可知B为这些的根,根据中序是ABDCE,可知A在B左,DCE在B右;

       3.分析DCE,根据先序CDE,可知C为根,根据中序DCE,可知D在左,E在右。

       4.分析GH,根据先序GH,可知,G为根,根据中序GH,可知H在G右边。

       5.至此,已经可以画出这个二叉树了,然后再根据后序遍历的顺序,遍历即可。

     5.翻转二叉树

    typedef struct treeNode{

        char data;

        treeNode struct *left;

        treeNode struct *right;

    }treeNode;


    void
    reverseTree(treeNode *root) { if (root == NULL) { return; } root->left = reverseTree(root->left); root->right = reverseTree(root->right); treeNode *temp = root->left; root->left = root->right; root->right = temp; return root; }

     6.查找二叉树的下一个节点

    题目:给定一棵二叉树和其中的一个结点,如何找出中序遍历顺序的下一个结点?树中的结点除了有两个分别指向左右子结点的指针以外,还有一个指向父节点的指针。

    思路:首先,了解中序遍历的规则是,左节点-》根-》右节点的顺序。然后考虑该节点的子节点情况,父节点情况。

    1.最理想的情况,该节点有右子节点,它的下一个结点就是它的右子树中的左子结点,则我们一直沿着指向做子节点指针,就能找到。

    2.如果该节点没有右子节点:则需要往上考虑父节点情况,往上找的过程中又分为两种情况:

       ①.他是父节点的左子节点,则父节点就是他的下一个节点。

      ②.他是父节点的右子节点,我们就需要沿着他父节点的指针一直向上找,直到找到一个节点,该节点是他父节点的左节点。

      ①②两种情况写代码的时候其实可以合并,从该节点向上查找,如果该节点是父节点的左节点,则该父节点就是要找的。

  • 相关阅读:
    windows8开发学习笔记
    文字超出隐藏并显示省略号
    list-style-type -- 定义列表样式
    抄录:系统集成项目管理42个子过程
    系统集成项目管理工程师--总结(4章)
    系统集成管理项目工程师总结--口诀2(包含1)
    系统集成管理项目工程师总结--法律法规日期篇1
    关于EAI(一个同学问了我很可爱的问题所以查到的这些)要看看
    系统集成项目管理工程师计划
    中级
  • 原文地址:https://www.cnblogs.com/howdoudo/p/7273024.html
Copyright © 2020-2023  润新知