算法
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.如果该节点没有右子节点:则需要往上考虑父节点情况,往上找的过程中又分为两种情况:
①.他是父节点的左子节点,则父节点就是他的下一个节点。
②.他是父节点的右子节点,我们就需要沿着他父节点的指针一直向上找,直到找到一个节点,该节点是他父节点的左节点。
①②两种情况写代码的时候其实可以合并,从该节点向上查找,如果该节点是父节点的左节点,则该父节点就是要找的。