代码改变世界

算法

2017-08-02 11:32  doudo  阅读(447)  评论(0编辑  收藏  举报

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

一、字符串

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个排列\t%s\n", 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("         全排列的递归实现\n");
    char szTextStr[] = "123";
    printf("%s的全排列如下:\n", 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个排列\t%s\n", 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("         去重全排列的递归实现\n");
char szTextStr[] = "122";
    printf("%s的全排列如下:\n", 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 == '\0')) {
        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 != '\0') {
        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.如果该节点没有右子节点:则需要往上考虑父节点情况,往上找的过程中又分为两种情况:

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

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

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