本文共 29500 字,大约阅读时间需要 98 分钟。
最近想回过头来看看以前写的一些代码,可叹为何刚进大学的时候不知道要养成写博客的好习惯。现在好多东西都没有做记录,后面也没再遇到相同的问题,忘的都差不多了。只能勉强整理了下面写的一些代码,这些代码有的有参考别人的代码,但都是自己曾经一点点敲的,挂出来,虽然很基础,但希望能对别人有帮助。
链表是一种非常基本的数据结构,被广泛的用在各种语言的集合框架中。
首先链表是一张表,只不过链表中的元素在内存中不一定相邻,并且每个元素都可带有指向另一个元素的指针。
链表有,单项链表,双向链表,循环链表等。
如下
1 typedef struct NODE{2 struct NODE * link;3 int value;4 } Node;
主要有增删查
1 Node * create(){ 2 Node * head,* p,* tail; 3 // 这里创建不带头节点的链表 4 head=NULL; 5 do 6 { 7 p=(Node*)malloc(LEN); 8 scanf("%ld",&p->value); 9 10 if(p->value ==0) break;11 // 第一次插入12 if(head==NULL)13 head=p;14 else15 tail->link=p;16 tail=p;17 }18 while(1);19 tail->link=NULL;20 return head;21 }22 23 int delet(Node **linkp,int del_value){24 register Node * current;25 Node * m_del;26 27 //寻找正确的删除位置,方法是按顺序访问链表,直到到达等于的节点28 while((current = *linkp)!=NULL && current->value != del_value)29 { 30 linkp = ¤t->link;31 }32 33 if(NULL==current)34 return FALSE;35 else36 {37 //把该节点删除,返回TRUE38 m_del=current->link;39 free(current);40 *linkp=m_del;41 }42 return TRUE;43 }44 //需要形参为链表头指针的地址和要插入的值45 int insert(Node **linkp,int new_value){46 register Node * current;47 Node * m_new;48 49 //寻找真确的插入位置,方法是按顺序访问链表,直到到达其值大于或等于新插入值的节点50 while((current = *linkp)!=NULL && current->value < new_value)51 { 52 linkp = ¤t->link;53 }54 //为新节点分配内存,并把新值存到新节点中,如果分配失败,返回FALSE55 m_new =(Node*)malloc(LEN);56 if(NULL==m_new)57 return FALSE;58 m_new->value = new_value;59 //把新节点放入链表,返回TRUE60 61 m_new->link = current;62 *linkp=m_new;63 64 return TRUE;65 }
仅仅只需要将尾指针指向头节点,就可以构成单项循环链表,即tail->link=head;,有的时候,可能需要将链表逆置,当然,如果需要逆置,最好一开始就做双向链表。
1 Node * reverse(Node * head){ 2 Node * p,*q; 3 q= head; 4 p = head->link; 5 head = NULL; 6 while(p) 7 { 8 // 接到新链表里面去 9 q->link = head; 10 head = q;11 // 继续遍历原来的链表12 q = p;13 p = p->link;14 }15 q->link = head; 16 head = q;17 return head;18 }
删除链表中所有值为x的节点,以及清除链表中重复的节点
1 // 功能: 删除链表中所有值为x的节点 2 // 形参: 1、若不带头结点,便是链表头指针的地址,即&head 3 // 2、若带头结点,便是链表头节点的next域的地址,即&head->next 4 // 形参: 为链表头指针的地址和要删除的值 5 void del_link(Node ** plink,int x){ 6 register Node * current; 7 while((current = *plink)!=NULL) 8 { 9 // 处理连续出现x的情况10 while(current && current->data == x){11 // 保留指向下一个节点的指针12 Node * temp = current;13 * plink = current = current->next;14 // 删除当前节点15 free(temp);16 }17 18 //向下遍历链表19 if (current)20 {21 plink = ¤t->next;22 }23 }24 }25 // 功能: 删除链表中重复多余的节点26 // 形参: 1、若不带头结点,便是链表头指针的地址,即&head27 // 2、若带头结点,便是链表头节点的next域的地址,即&head->next28 void del_linkAll(Node ** plink){29 register Node * current;30 while((current = *plink) != NULL){31 //注意,这里取指向下一个元素的指针的地址,这样删除是会保留这一个节点32 del_link(¤t->next,current->data);33 plink = ¤t->next;34 }35 }
对于双向链表,也就是在节点中再添加一个节点,让它与另一个指针指向的方向相反。当然,当节点有了两个节点之后,就可以构成更复杂的比如树图等复杂结构了,双向链表可像如下定义
1 #ifndef __LINKLISTEX_H__ 2 #define __LINKLISTEX_H__ 3 #include4 using std::string; 5 //================双向链表的定义=============== 6 template 7 class DulLinkList 8 { 9 private:10 typedef struct DulNode{11 struct DulNode * prior;12 T data;13 struct DulNode * next;14 }DulNode;15 DulNode * frist;16 void Init();17 void Del(DulNode * delNode);18 public:19 DulLinkList();20 ~DulLinkList();21 void AddElem(const T & data);22 void DelElem(const T & data);23 string ToString()const;24 protected:25 };26 #endif//__LINKLISTEX_H__
对双向链表的操作也无外乎增删改
1 #include "LinkListEx.h" 2 #include3 using namespace std; 4 5 template 6 DulLinkList ::DulLinkList(){ 7 Init(); 8 } 9 10 template 11 void DulLinkList ::Init(){12 // 初始化第一个结点13 this->frist = new DulNode;14 this->frist->prior = NULL;15 this->frist->next = NULL;16 }17 18 template 19 void DulLinkList ::AddElem(const T & data){20 // 直接头部插入节点21 DulNode * newNode = new DulNode;22 newNode->data = data;23 newNode->next = this->frist;24 newNode->prior = NULL;25 this->frist->prior = newNode;26 this->frist = newNode;27 }28 29 30 template 31 void DulLinkList ::DelElem(const T & data){32 DulNode * current = this->frist->next;33 while (current != NULL && current->data != data) {34 current = current->next;35 }36 if (!current)37 {38 return;39 }40 Del(current);41 }42 43 template 44 void DulLinkList ::Del(DulNode * delNode){45 // 调整当前节点两端的节点的指针46 delNode->prior->next = delNode->next;47 delNode->next->prior = delNode->prior;48 delete delNode;49 }50 template 51 DulLinkList ::~DulLinkList(){52 DulNode * current = this->frist;53 while (current)54 {55 DulNode * old = current;56 current = current->next;57 delete old;58 }59 }60 61 template 62 string DulLinkList ::ToString()const{63 string res;64 DulNode * current = this->frist->next;65 while (current)66 {67 res.append(1,current->data);68 current = current->next;69 }70 return res;71 }
链表是个很基础的东西,后面一些复杂的算法或数据结构的本质也是一个链表。链表和顺序表(也就是数组)都可以再进一步抽象成更复杂的数据结构。
比如队列和栈,不过是在链表或顺序表的基础上限制单端操作而已。再比如,由链表和顺序表还可以构成二叉树堆,它们还可以组合使用构成邻接表,十字链表,邻接多重表等结构用来描述图,等等。
做里快两年web开发了,可以说字符串是用多最多的数据类型了,所以针对字符串的算法也非常的多。先从简单的慢慢来。
首先最基本的是对字符串的求长,连接,比较,复制等
1 // 统计字符串长度 2 int str_len(char *str){ 3 return *str ? str_len(str+1)+1 : 0 ; 4 } 5 // 字符串复制 6 void str_cpy(char *str1,char *str2){ 7 while(*str1++ = *str2++); //当str2指向'\0'时,赋值给*str1 表达式的值为0 即为假。退出循环 8 //if(*str1 == '\0') // 考虑到 串2的长度大于串1的长度,防止指针越界 9 //break;10 }11 // 字符串比较12 int str_cmp(char *str1,char *str2){13 int i;// i指向字符不同时数组下标14 for(i=0;str1[i]==str2[i] && str1[i]!='\0' && str2[i]!='\0';i++);15 return str1[i]-str2[i];16 }17 // 字符串连接18 void str_cat(char *str1,char *str2){19 while(*str1 != '\0')20 str1++;21 while(*str1++ = *str2++);22 }
字符串比较复杂一点的就是模式匹配和子序列(编辑距离)的问题。
首先是较为简单的BF算法,这种算法原理非常简单,比如连个串a(主串)和b(模式串),首先将a1和b1进行比较,如果相同,则将b2与a2进行比较,如果还相同,继续拿a3与b3比,直到b串匹配完,怎匹配完成,如果出现不同的,怎回到最初的状态,将b1与a2进行比较,将b2与a3比较,等等,如此反复直到失败或成功。
1 typedef struct{ 2 char * str; 3 int length; 4 }String; 5 6 int Index_BF(String mainStr,String modeStr,int pos){ 7 int i = pos-1; 8 int j = 0; 9 while (i
较为复杂的算法是KMP算法,KMP算法的关键是避免BF算法中的回朔。并且当匹配失败后向右滑动到一个能自左最大对齐的位置继续匹配。
若在ai,bj的位置匹配失败,所以已经匹配的串便是
B1 B2 ... Bj-1 == Ai-j+1 Ai-j+2 ... Ai-1;
假设滑动完后要让Bk与Ai对齐,则应该有
B1 B2 B3 ... Bk-1 == Ai-k+1 A-k+2 ... Ai-1;
因为是向右滑动,想一想i的位置不变,B向右滑动,很显然,k要小于j。
所以进一步可以得到k到j之间B的子串(Bj前面的k-1个字符)与Ai前的k-1个字符是相同的,即
Bj-k+1 Bj-k+2 ... Bj-1 == Ai-k+1 Ai-k+2 ... Ai-1;
所以有
B1 B2 B3 ... Bk-1 == Bj-k+1 Bj-k+2 ... Bj-1
可以看出来,这个有个特点,字符串 B1 B2 ..... Bj-1关于Bk有种对称的感觉,不过这个不是镜像对称,不过我还是喜欢这么记`对称`,这也是求next值的依据,这个next就是k,就是偏移值。
next(j) = 0 (j==1) || max{k|1<=k<j && B1 B2 B3 ... Bk-1 == Bj-k+1 Bj-k+2 ... Bj-1} || 1;
下面是完整的KMP算法
1 void GetNext(const char * mode,int * next){ 2 //求模式mode的next值并存入数组next中 3 int i=1,j=0; 4 next[1] = 0; 5 while(i < mode[0]){ 6 if(0 == j || mode[i] == mode[j]){ 7 i++;j++; 8 next[i] = j; 9 }10 else11 j = next[j];12 }13 }14 int Index_KMP(const char * str,const char * mode,int pos){15 int i=pos,j=1;16 int * next = (int *)malloc(sizeof(int)*(mode[0]+1));17 next[0]=mode[0];18 GetNext(str,next);19 while (i<=str[0] && j<= mode[0]) {20 if (0==j || str[i] == mode[j]) {21 i++;j++;22 }23 else{24 // 滑动模式串,注意next[j]是小于j的,这才是向右滑动25 j = next[j];26 }27 }28 return j>mode[0] ? i - mode[0] : 0;29 }30 31 void main(){32 char string[16] = "\016abcabcabaabcac";33 char mode[10] = "\010abaabcac";34 printf("模式串在主串中的位置:%d\n",Index_KMP(string,mode,1));35 }
下面的问题是最长公共子序列,算法的思想是动态规划,核心是转义方程
,也就是当两个字符相等时取左上元素+1,不相等时取左和上中大的那个
1 #include2 #include 3 #define MAXN 128 4 #define MAXM MAXN 5 int a[MAXN][MAXM]; 6 int b[MAXN][MAXM]; 7 char * str1 = "ABCBDAB"; 8 char * str2 = "BDCABA"; 9 10 int LCS(const char *s1,int m, const char *s2,int n)11 {12 int i, j;13 a[0][0] = 0;14 for( i=1; i <= m; ++i ) {15 a[i][0] = 0;16 }17 memset(b,0,sizeof(b));18 for( i=1; i <= n; ++i ) {19 a[0][i] = 0;20 }21 for( i=1; i <= m; ++i ){22 for( j=1; j <= n; ++j ){23 if(s1[i-1]==s2[j-1]){ //如果想等,则从对角线加1得来24 a[i][j] = a[i-1][j-1]+1;25 b[i][j] = 1;26 }27 else if(a[i-1][j]>a[i][j-1]){ //否则判段它上面、右边的值,将大的数给他28 a[i][j]= a[i-1][j];29 b[i][j] = 2;30 }31 else{32 a[i][j] = a[i][j-1];33 b[i][j] = 3;34 }35 36 }37 }38 return a[m][n];39 }40 char str[MAXN];41 int p=0;42 void cSubQ(int i,int j){43 if(i<1 || j<1) return;44 if(1==b[i][j]){45 cSubQ(i-1,j-1);46 str[p++]=str1[i-1];47 }48 else if(2 == b[i][j])49 {50 cSubQ(i-1,j);51 }52 else{53 cSubQ(i,j-1);54 }55 }56 int main()57 {58 int m = strlen(str1), n = strlen(str2);59 LCS(str1,m,str2,n);60 cSubQ(m,n);61 str[p] = '\0';62 printf("%s\n",str);63 return 0;64 }
很显然,这个算法的时间复杂度和空间复杂度为o(n*m)
树这里主要以二叉树为例,二叉树算是一种特殊的树,一种特殊的图。
二叉树具备如下特征
用数组和链表都可以存储二叉树,但我见过的算法大都用数组存储二叉树,想必链表虽然易于理解,但相比写起算法来未必好写。
有增删查遍历等操作,代码如下。
1 typedef struct bitnode{ 2 int m_iDate; 3 struct bitnode * m_lChild/*左孩子指针*/,* m_rChild/*右孩子指针*/; 4 } CBiTNode; 5 6 7 //建立一个带头结点的空的二叉树 8 CBiTNode * Initiate(){ 9 CBiTNode * bt; 10 bt = (CBiTNode*)malloc(sizeof CBiTNode); 11 bt->m_iDate = 0; 12 bt->m_lChild = NULL; 13 bt->m_rChild = NULL; 14 return bt; 15 } 16 17 /* 18 //建立一个不带头结点的空的二叉树 19 CBiTNode * Initiate(){ 20 CBiTNode * bt; 21 bt = NULL; 22 return bt; 23 } 24 */ 25 26 //生成一棵以x为根节点数据域信息,以lbt和rbt为左右子树的二叉树 27 CBiTNode * Create(int x,CBiTNode * lbt,CBiTNode * rbt){ 28 CBiTNode * p; 29 if((p=(CBiTNode*)malloc(sizeof CBiTNode)) ==NULL) 30 return NULL; 31 p->m_iDate = x; 32 p->m_lChild = lbt; 33 p->m_rChild = rbt; 34 return p; 35 } 36 37 //在二叉树bt中的parent所指节点和其左子树之间插入数据元素为x的节点 38 bool InsertL(int x,CBiTNode * parent){ 39 CBiTNode * p; 40 41 if(NULL == parent){ 42 printf("L插入有误"); 43 return 0; 44 } 45 46 if((p=(CBiTNode*)malloc(sizeof CBiTNode)) ==NULL) 47 return 0; 48 49 p->m_iDate = x; 50 p->m_lChild = NULL; 51 p->m_rChild = NULL; 52 53 if(NULL == parent->m_lChild) 54 parent->m_lChild = p; 55 else{ 56 p->m_lChild = parent->m_lChild; 57 parent->m_lChild = p; 58 } 59 60 return 1; 61 } 62 63 //在二叉树bt中的parent所指节点和其右子树之间插入数据元素为x的节点 64 bool InsertR(int x,CBiTNode * parent){ 65 CBiTNode * p; 66 67 if(NULL == parent){ 68 printf("R插入有误"); 69 return 0; 70 } 71 72 if((p=(CBiTNode*)malloc(sizeof CBiTNode)) ==NULL) 73 return 0; 74 75 p->m_iDate = x; 76 p->m_lChild = NULL; 77 p->m_rChild = NULL; 78 79 if(NULL == parent->m_rChild) 80 parent->m_rChild = p; 81 else{ 82 p->m_rChild = parent->m_rChild; 83 parent->m_rChild = p; 84 } 85 86 return 1; 87 } 88 89 //在二叉树bt中删除parent的左子树 90 bool DeleteL(CBiTNode *parent){ 91 CBiTNode * p; 92 if(NULL == parent){ 93 printf("L删除出错"); 94 return 0; 95 } 96 97 p = parent->m_lChild; 98 parent->m_lChild = NULL; 99 free(p);//当*p为分支节点时,这样删除只是删除了子树的根节点。子树的子孙并没有被删除100 101 return 1;102 }103 104 //在二叉树bt中删除parent的右子树105 bool DeleteR(CBiTNode *parent){106 CBiTNode * p;107 if(NULL == parent){108 printf("R删除出错");109 return 0;110 }111 112 p = parent->m_rChild;113 parent->m_rChild = NULL;114 free(p);//当*p为分支节点时,这样删除只是删除了子树的根节点。子树的子孙并没有被删除115 116 return 1;117 }118 119 //二叉树的遍历120 //先序遍历二叉树121 bool PreOrder(CBiTNode * bt){122 if(NULL == bt)123 return 0;124 else{125 printf("bt->m_iDate == %d\n",bt->m_iDate);126 PreOrder(bt->m_lChild);127 PreOrder(bt->m_rChild);128 return 1;129 }130 }
对二叉树可以有先序遍历,中序遍历,后序遍历得到的序列中每个元素互第一个和最后一个节点外都会有一个前驱和后驱节点。如果把前驱节点和后驱节点的信息保存在节点中就构成了线索二叉树,显然只要免礼一遍就能得到线索二叉树。
二叉树比较经典的有哈夫曼编码问题,二叉堆等问题,二叉堆放到堆排序一起讲。
哈夫曼问题就是要让频率高的节点编码最短,也就是要节点在哈夫曼树中的深度最小。
1 // Huffman.h 2 #ifndef __HUFFMAN_H__ 3 #define __HUFFMAN_H__ 4 5 typedef struct { 6 unsigned int weight; 7 unsigned int parent,lchild,rchild; 8 }HTNode,* HuffmanTree; // 动态分配数组储存哈夫曼树 9 typedef char * *HuffmanCode; // 动态分配数组储存哈夫曼编码表10 11 #endif//__HUFFMAN_H__
1 // HuffmanTest.cpp 2 #include "Huffman.h" 3 #include4 #include 5 // 函数功能:在哈夫曼编码表HT[1...range]中选择 parent 为0且weight最小的两个结点,将序号分别存到s1和s2里 6 void Select(const HuffmanTree &HT,int range,int &s1,int &s2){ 7 int i; 8 int * pFlags; 9 pFlags = (int *)malloc((range+1)*sizeof(int));10 int iFlags=0;11 for(i=1;i<=range;i++){12 if(0 == HT[i].parent){13 pFlags[++iFlags] = i;14 }15 }16 int Min=1000;17 int pMin=pFlags[1];18 for(i=1;i<=iFlags;i++){19 if(HT[pFlags[i]].weight < Min){20 pMin = i;21 Min=HT[pFlags[i]].weight;22 }23 }24 s1=pFlags[pMin];25 Min=1000;26 for(i=1;i<=iFlags;i++){27 if(pFlags[i]!=s1)28 if(HT[pFlags[i]].weight < Min){29 pMin = i;30 Min=HT[pFlags[i]].weight;31 }32 }33 s2=pFlags[pMin];34 }35 void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int * w, int n){36 // w存放n个字符的权值(均>0),构造哈夫曼树HT,并求出n个字符的哈夫曼编码HC37 if(n <= 1) return;38 int m = 2*n-1;39 HT = (HuffmanTree)malloc((m+1)*sizeof(HTNode)); // 0 单元不使用40 int i;41 HuffmanTree p=NULL;42 // 初始化哈夫曼编码表43 for(p=HT+1,i=1;i<=n;i++,p++,w++){44 p->weight = *w,p->parent = p->lchild = p->rchild = 0;45 }46 for( ;i<=m;i++,p++){47 p->weight = p->parent = p->lchild = p->rchild = 0;48 }49 // 建立哈夫曼树50 int s1,s2;51 for(i=n+1;i<=m;i++){52 Select(HT,i-1,s1,s2);53 HT[s1].parent = HT[s2].parent = i;54 HT[i].lchild = s1,HT[i].rchild = s2;55 HT[i].weight = HT[s1].weight+HT[s2].weight;56 }57 58 // 从叶子节点到根逆向求每个字符的哈夫曼编码59 HC = (HuffmanCode)malloc((n+1)*sizeof(char *)); // 分配n个字符编码的头指针向量60 char * cd = (char*)malloc(n*sizeof(char)); // 分配求编码的工作空间61 int start; // 编码起始位置62 cd[n-1] = '\0'; // 编码结束符63 for(i=1;i<=n;i++){ // 逐个字符求哈夫曼编码64 start = n-1; // 将编码起始位置和末位重合65 for(int c=i,f=HT[i].parent;f != 0; c=f,f=HT[f].parent){66 if(c == HT[f].lchild)67 cd[--start] = '0';68 else69 cd[--start] = '1';70 }71 HC[i] = (char*)malloc((n-start)*sizeof(char)); // 为第i个字符编码分配空间72 strcpy(HC[i],&cd[start]); // 从 cd 复制字符串到 HC73 }74 free(cd);75 }
哈夫曼树的测试数据
1 #include "Huffman.h" 2 #include3 #include 4 5 void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int * w, int n); 6 void main(){ 7 system("color F0"); 8 HuffmanTree HT = NULL; 9 HuffmanCode HC = NULL;10 char chArr[8]={ 'A','B','C','D','E','F','G','H'};11 int w[8]={ 7,19,2,6,32,3,21,10};12 HuffmanCoding(HT,HC,w,8);13 int i,j;14 printf( "HT weight parent lchild rchild\n");15 for(i=1;i<=15;i++){16 printf("%02d\t%2u\t%2u\t%2u\t%2u\n",i,HT[i].weight,HT[i].parent,HT[i].lchild,HT[i].rchild);17 }18 printf("\n\n字符 权值 编码\n");19 for(i=0;i<8;i++){20 printf("%c\t%2d\t%-8s\n",chArr[i],w[i],HC[i+1]);21 }22 }
图是一种比较复杂的数据结构,图的定义就不在此复述了。
下面是上面四种描述的代码表示
1 #ifndef __GRAPH_DEFINE_H__ 2 #define __GRAPH_DEFINE_H__ 3 4 #define INT_MAX 9999 5 #define INFINITY INT_MAX // 最大值 6 #define MAX_VERTEX_NUM 20 // 最大顶点个数 7 #define VEX_FORM "%c" // 顶点输入格式 8 #define INFO_FORM "%d" // 边信息输入格式 9 typedef int InfoType; // 弧相关信息类型10 typedef char VextexType; // 顶点数据类型11 typedef int VRType; // 顶点关系类型,对无权图,用0或者1表示是否相邻。对带权图,则是权值类型。12 typedef enum { DG,DN,UDG,UDN} GraphKind;// 图类型 {有向图,有向网,无向图,无向网}13 14 //15 // 邻接矩阵存储结构: 可存储任意类型图16 //17 typedef struct{18 VRType Adj;19 InfoType Info; // 该弧相关信息20 }ArcCell,AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];21 typedef struct{22 char cVexs[MAX_VERTEX_NUM]; // 顶点向量23 AdjMatrix arcs; // 邻接矩阵24 int iVexNum,iArcNum; // 图中当前顶点数和弧数25 GraphKind kind; // 图的种类标志26 }MGraph;27 28 29 //30 // 邻接表存储结构: 可存储任意类型的图31 //32 typedef struct ArcNode{33 int iAdjvex; // 该弧所指向的顶点位置34 struct ArcNode *nextarc; // 指向下一条弧的指针35 InfoType Info; // 该弧相关信息36 }ArcNode;37 typedef struct VNode{38 VextexType cData; // 顶点信息39 ArcNode *firstarc; // 指向第一条依附该顶点的弧的指针40 }VNode,AdjList[MAX_VERTEX_NUM];41 typedef struct {42 AdjList vertices;43 int iVexnum,iArcnum; // 图的当前顶点个数和弧数44 GraphKind Kind; // 图的种类标志45 }ALGraph;46 47 //48 // 十字链表存储结构: 只存储有向图49 //50 typedef struct ArcBox{51 int iTailVex,iHeadVex; // 该弧的尾和头顶点的位置52 struct ArcBox *hLink,*tLink; // 分别为弧头相同和弧尾相同的链域53 InfoType Info; // 该弧相关信息54 }ArcBox;55 typedef struct VexNode{56 VextexType data;57 ArcBox *firstIn,*firstOut; // 分别指向了该顶点的第一条入弧和出弧58 }VexNode;59 typedef struct OLGraph{60 VexNode xlist[MAX_VERTEX_NUM]; // 表头向量61 int iVexNum,iArcNum; // 有向图当前顶点数和弧数62 }OLGraph;63 64 //65 // 邻接多重表: 存储无向图66 //67 typedef enum {unvisited,visited}VisitIf;68 typedef struct EBox{69 VisitIf mark; // 访问标记70 int iIVex,iJVex; // 边依附的两个顶点的位置71 struct EBox *iLink,*jLink; // 分别指向依附这两个顶点的下一条边72 InfoType Info; // 该边信息指针73 }EBox;74 typedef struct VexBox{75 VextexType data;76 EBox *firstEdge; // 指向第一条依附该顶点的边77 }VexBox;78 typedef struct {79 VexBox adjmulist[MAX_VERTEX_NUM];80 int iVexNum,iEdgeNum; // 无向图当前顶点数和边数81 }82 #endif//__GRAPH_DEFINE_H__
对图的操作有创建,增删查等等,其中查就是遍历,遍历分为深度优先搜索和广度优先搜索。
深度优先搜索类似于树的钟旭遍历,即遇到未范围的节点,马上访问,修改标记数组,然后沿着这个节点继续访问,直到访问完,然后在回朔,转向未访问的分支。直到节点被访问完,如果是连通图,只要访问进行一次深度搜索,如果是非连通的,就要搜索多次。
广度优先搜索就像金字塔从上向下的一层一层的搜索,广度优先搜索除了需要用标记数组记录状态以外,还需要用队列来将发现而未访问的节点记录下来。用队列是为了保证遍历顺序。
下面是一些图相关的操作算法
1 #include2 #include "GraphDefine.h" 3 #include "Define.h" 4 #include 5 6 // 7 // 邻接矩阵图的相关操作 8 // 9 Status CreateDG(MGraph &G); // 构造有向图 10 Status CreateDN(MGraph &G); // 构造有向网 11 Status CreateUDG(MGraph &G); // 构造无向图 12 Status CreateUDN(MGraph &G); // 构造无向网 13 int LocateVex(const MGraph &G,const VextexType &v); // 获得顶点v在图中的位置 14 Status CreateGraph(MGraph &G,VextexType v){ 15 // 采用数组(邻接矩阵)表示法,构造图G 16 int iType; 17 scanf("%d%*c",&iType); 18 G.kind = (GraphKind)iType; 19 switch(G.kind) { 20 case DG: return CreateDG(G); // 构造有向图 21 case DN: return CreateDN(G); // 构造有向网 22 case UDG:return CreateUDG(G); // 构造无向图 23 case UDN:return CreateUDN(G); // 构造无向网 24 default: 25 return ERROR; 26 } 27 } 28 29 Status CreateUDN(MGraph &G){ 30 // 采用数组(邻接矩阵)表示法,构造网G 31 int i,j,k; 32 int IncInfo; 33 scanf("%d %d %d",&G.iVexNum,&G.iArcNum,&IncInfo); 34 for(i=0;i 的权值 48 if(IncInfo) scanf(INFO_FORM,G.arcs[i][j].Info); 49 G.arcs[j][i] = G.arcs[i][j]; // 置 对称弧 50 } 51 return OK; 52 } 53 54 // 55 // 十字链表图的相关操作 56 // 57 int LocateVex(const OLGraph &G,const VextexType &v); // 获得顶点v在图中的位置 58 59 Status CreateDG(OLGraph &G){ 60 // 采用十字链表存储表示,构造有向图 G(G.kind = DG) 61 InfoType IncInfo; 62 scanf("%d %d %d",G.iVexNum,G.iArcNum,&IncInfo); 63 int i; 64 for(i=0;i iTailVex = v1,p->iHeadVex = v2; 77 p->hLink = G.xlist[j].firstIn,p->tLink = G.xlist[i].firstOut; 78 p->Info = NULL; 79 G.xlist[j].firstIn = G.xlist[i].firstOut = p; // 完成在入弧与出弧的链头的插入 80 if(IncInfo) scanf(INFO_FORM,&p->Info); //若含有相关信息,则输入 81 } 82 return OK; 83 } 84 85 // 86 // 深度优先搜索 87 // 88 bool visited[MAX_VERTEX_NUM]; 89 Status(*VisitFunc)(int v); 90 int FirstAdjVex(MGraph,int); 91 int NextAdjVex(MGraph,int); 92 93 void DFSTeaverse(MGraph G,Status (*Visit)(int v)){ 94 int v; 95 VisitFunc = Visit; 96 for(v=0;v 0 ; w = NextAdjVex(G,v))106 if(!visited[w]) DFS(G,w);107 }108 //109 // 广度优先搜索110 //111 // 队列相关函数112 void InitQueue(int []);113 void EnQueue(int []);114 bool QueueEmpty(int []);115 void DFSTeaverse(MGraph G,Status (*Visit)(int v)){116 int v;117 for (v=0;v =0 ; w = NextAdjVex(G,u))132 if(!visited[w]){133 visited[w] = true;134 Visit(w);135 EnQueue(Q,w);136 }137 }138 }139 }140 }
与图相关的还有很多算法,比如求最小生成树的prim算法和kruskal算法
prim算法初始化一个s集合,始终挑选与s集合相连最小的边连接的节点加到集合中,然后更新剩余节点到s的距离,直到所有的点添加进了s集合,prim算法代码如下
1 #include2 #include 3 #define MAXN 1024 4 #define INF 0xeFFFFFFF 5 int e[MAXN][MAXN]; 6 int low[MAXN]; 7 bool inSet[MAXN]; 8 int n; 9 int prim(){10 int res=0,i,j,k;11 inSet[0] = true;12 memset(inSet,0,sizeof(inSet));13 for(i=0;i e[p][k]){34 low[p] = e[p][k];35 }36 }37 }38 39 return res;40 }41 42 int main(void){43 int i;44 scanf("%d",&n);45 for(i=0;i
Kruskal算法不断选取最小的边i,只要biani加进来不构成回路,则加入到边的集合e中来,直到加入的边能连接所有的顶点,则结束算法
1 #include2 #include 3 #include 4 #define MAXN 1024 5 #define INF 0xeFFFFFFF 6 7 //定义边的结构 8 typedef struct Ele{ 9 int x,y; //边的端点 10 int w; //边的权重 11 bool inSet; 12 }Ele; 13 14 Ele * eles[MAXN];//对于n个顶点,最多有n*(n-1)条边 15 16 int m;//m条边 17 18 int pa[MAXN]; 19 int r[MAXN]; 20 21 void make_set(){ 22 int i; 23 for(i=0;i r[yp]){ 42 pa[yp] = xp;//zhi小的放在zhi大的下面 43 } 44 else{ 45 pa[xp] = yp; 46 if(r[xp] == r[yp]){ 47 r[yp]++; 48 } 49 } 50 return true; 51 } 52 53 54 55 void sort(){ 56 int i,j; 57 for(i=0;i eles[j].w){ 61 p = j; 62 } 63 } 64 if(p!=i){ 65 Ele * tmp = eles[i];eles[i] = eles[p];eles[p] = tmp; 66 } 67 } 68 } 69 /* 70 int cmp(void * a,void * b){ 71 return (Ele*)a->w - (Ele*)b->w; 72 } 73 */ 74 int klske(){ 75 //将边由小到大排序 76 //qsort(eles,sizeof(eles),sizeof(eles[0]),cmp) 77 sort(); 78 int res; 79 for(int i=0;i x = x; 96 eles[i]->y = y; 97 eles[i]->w = w; 98 eles[i]->inSet = false; 99 }100 int res = klske(k);101 printf("%d\n",res);102 103 for(i=0;i
上面主要涉及的是一些数据结构,以及这些数据结构最基本的算法,下面进入算法部分
线索二叉树
线索二叉树要求任何几节点的左子树比该节点的值小,右子树的值比该节点大。二叉排序树,主要涉及的是插入和搜索
1 #include2 #include 3 4 typedef struct bsTree{ 5 int m_iDate; 6 struct bsTree * m_lChild/*左孩子指针*/,* m_rChild/*右孩子指针*/; 7 } * BsTree,BsNode ; 8 9 BsTree insert(BsTree bs,int x){10 BsNode * p = bs;11 BsNode * note = p;12 BsNode * ct = NULL;13 while(p){14 if(x == p->m_iDate){15 return p;16 }17 else{18 // 记录上一个节点19 note = p;20 if(x < p->m_iDate) p = p->m_lChild;21 else p = p->m_rChild;22 }23 }24 25 ct = (BsNode * )malloc(sizeof(BsNode));26 ct->m_iDate = x;27 ct->m_rChild = NULL;28 ct->m_lChild = NULL;29 if(!bs){30 bs = ct;31 }else if(x < note->m_iDate){32 note->m_lChild = ct;33 }else note->m_rChild = ct;34 return bs;35 }36 37 BsNode * search(BsTree bs , int x){38 if(!bs || bs->m_iDate == x){39 return bs;40 }else if(x < bs->m_iDate){41 return search(bs->m_lChild,x);42 }else{43 return search(bs->m_rChild,x);44 }45 }
二分查找
1 int binarySearch(int arr[],int l,int r,int key){ 2 while(l> 1; 4 if(arr[mid] > key){ 5 r = mid -1; 6 }else if(arr[mid] < key){ 7 l = mid + 1; 8 }else{ 9 return mid;10 }11 }12 return -l-1; // 返回可插入位置13 }
以升序为例,冒泡排序每次扫描数组,将逆序修正,每次恰好将最大的元素过五关斩六将推到最后,再在剩余的元素重复操作
1 void mpSort(int a[],int n){ 2 int i,j,swaped=1; 3 for(i=0;ia[j+1]){ 7 swap(a[j],a[j+1]); 8 swaped = 1; 9 }10 }11 }12 }
可见,冒泡排序的平均时间复杂度为O(n^2)
以升序为例,每次扫描数组,找到最小的元素直接挪到第一个来,再在剩余的数组中重复这样的操作
1 void selectSort(int a[],int n){ 2 int i,j; 3 for(i=0;ia[j]){ 7 p = j; 8 } 9 }10 if(p != i){11 swap(p,i);12 }13 }14 }
选择排序的平均时间复杂度也是O(n^2)
直接插入排序不断在前面已经有序的序列中插入元素,并将元素向后挪。再重取一个元素重复这个操作
1 #define MAXSIZE 20 2 3 typedef int KeyType; 4 typedef struct { 5 KeyType key; 6 }RedType; 7 typedef struct{ 8 RedType r[MAXSIZE+1]; 9 int length;10 }SqList;11 //---------------------直接插入排序---------------------12 void InsertSort(SqList & L){13 int i,j;14 for (i=2;i<=L.length;i++)15 {16 L.r[0] = L.r[i];17 j = i -1;18 while (L.r[0].key < L.r[j].key)19 {20 L.r[j+1] = L.r[j];21 j--;22 }23 L.r[j+1] = L.r[0];24 }25 }
插入排序的平均时间复杂度也是O(n^2)
堆排序也是一种插入排序,不过是向二叉堆中插入元素,而且以堆排序中的方式存储二叉堆,则二叉堆必定是一棵完全二叉树,堆排序设计的主要操作就是插入和删除之后的堆调整,使得堆保持为大底堆或者小底堆的状态。也就是根节点始终保持为最小或最大,每次删除元素,就将根节点元素与最后元素交换,然后将堆的大小减1,然后进行堆调整,如此反复执行这样的删除操作。很显然,大底堆最后会得到递增序列,小底堆会得到递减序列。
1 /** 2 * 堆调整 3 * 在节点数为n的堆中从i节点开始进行堆调整 4 */ 5 void heapAjust(int arr[] ,int i,int n){ 6 // 从i节点开始调整堆成为小底堆 7 int tmp = arr[i],min; 8 // 左孩子,对于节点i,它的左孩子就是2i+1,右孩子就是2i+2; 9 for(;(min = 2*i + 1)arr[min+1]) min ++;11 if(arr[i] > arr[min]){12 // 将小元素放置到堆顶13 arr[i] = arr[min];14 }15 else break;16 }17 arr[i] = tmp;18 }19 void heapSort(int arr[],int n){20 // 建堆21 int i;22 for(i=n>>1;i>=0;i--){23 heapAjust(arr,i,n);24 }25 // 取堆顶,调整堆26 for(i = n-1;i>0;i--){27 // 每次取堆顶最小的元素与最后的元素交换,最后会得到递减序列28 int tmp = arr[0];arr[0] = arr[i],arr[i] = tmp;29 // 删除一个元素后需要从根元素起重新调整堆30 heapAjust(arr,0,i);31 }32 }
排序的平均时间复杂度为O(NLogN)
说到快排,就想起了大一上学期肖老师(教我C语言的老师)与我讨论的问题,当时懵懂无知,后才才知道那就是快排。
快排的思想也很简单,以升序为例,在序列中选一标杆,一般讲第一个元素作为标杆,然后将序列中比标杆小的元素放到标杆左边,将比标杆大的放到标杆右边。然后分别在左右两边重复这样的操作。
1 void ksort(int a[],int l,int r){ 2 // 长度小于2有序 3 if(r - l < 2) return; 4 int start = l,end = r; 5 while(l < r){ 6 // 向右去找到第一个比标杆大的数 7 while(++l < end && a[l] <= a[start]); 8 // 向左去找第一个比标杆小的数 9 while(--r > start && a[r] >= a[start]);10 if(l < r) swap(a[l],a[r]); // 前面找到的两个数相对于标杆逆序 ,需交换过来 。l==r 不需要交换,11 }12 swap(a[start],a[r]);// 将标杆挪到正确的位置.13 // 对标杆左右两边重复算法,注意,这个l已经跑到r后面去了14 ksort(a,start,r);15 ksort(a,l,end);16 }
快速排序的平均时间复杂度为O(NLogN)
合并排序采用分治法的思想对数组进行分治,对半分开,分别对左右两边进行排序,然后将排序后的结果进行合并。按照这样的思想,递归做是最方便的。
1 int a[N],c[N]; 2 void mergeSort(l,r){ 3 int mid,i,j,tmp; 4 if(r - 1 > l){ 5 mid = (l+r) >> 1; 6 // 分别最左右两天排序 7 mergeSort(l,mid); 8 mergeSort(mid,r); 9 // 合并排序后的数组10 tmp = l;11 for(i=l,j=mid;ia[j]) c[tmp++] = a[j++];13 else c[tmp++] = a[i++];14 }15 // 把剩余的接上16 if(j < r) {17 for(;j
先到这里吧,写这么多有点辛苦,白天还有事
转载地址:http://fibso.baihongyu.com/