不好意思,這兩天要幫家裡做一些活兒。而且內容量與操作量也確實大幅提升了。所以寫得很慢。
不過,從今天開始。我寫的東西,許多都是之前沒怎麼學的了。所以速度會慢下來,同時寫得也會詳細許多。
第三章是數據結構,數據結構可以說是理論學習的重點。同時許多學校(包括我所就讀的大學)都開設了數據結構課程。但是講的東西大多太過理論性,主要講解概念與思想。
另外,數據結構可是計算機考研中專業課的重點科目哦。
這章數據結構包括結構體、鏈表、棧與隊列、串與廣義表、二叉樹、圖與圖的應用。
每一個都是重點,所以我會細細地過一遍。
3.1結構體
也許你的C語言老師不會講解鏈表、二叉樹等,但他一定會講解結構體的使用,並且讓你們作出代碼實現。
結構體是什麼?其實結構體就是一個制式容器。裡面包括了規定的各個變量。
比如說,我設定了
1 struct student
2 {
3 char name;
4 int age;
5 float score;
6 };
那麼在這個結構體所在的程序裡,student就是一個包含三個不同數據類型的變量。當然,你也可以認為student是一個新的數據類型,一個自己設立的數據類型。不過這個數據類型是一個特定的數組,因為其中有著許多不同的數據類型(數組只能是相同數據類型)。
創建結構體是要用到struct。其他用法將在接下來的例子中提到。
實例076
問題:從鍵盤中輸入姓名和電話號碼,以#結束,編程實現輸入姓名,查詢電話號碼的功能。
邏輯:首先一個主函數作為程序入口,實現程序運行框架。然後一個存儲函數readin實現數據的存儲,再一個查詢函數seach實現數據的查找。
主函數無非定義,初始化變量。調用存儲函數來存儲我們輸入的函數。再調用查詢函數來輸出所要的結果。
存儲函數定義,初始化變量。建立循環,判讀輸入是否為#,同時將數據存儲。
查詢函數定義,初始化變量。建立循環,判斷所要查找的數據與當前數據是否符合,判斷數據是否已經全部遍歷。
程序代碼:
1 #include<stdio.h>
2 #include<string.h>
3 #define MAX 101
4 struct aa
5 {
6 char name[15];
7 char tel[15];
8 };
9 int readin(struct aa *a)
10
11 //注意這裡結構體aa的變量名為*a,是一個指針,因為這個結構體在三個函數中均被調用。所以只有使用指針才較為便利地可以實現函數間大量數據的調用。
12
13 //其實,在使用指針為變量時,就已經建立了一個以a為名的aa型結構體數組了。
14
15 {
16 int i=0,n=0;
17 while(1)
18
19 //本人最為喜歡的循環體設置方法,包括for(;1;)的設立,循環體中再建立相關判斷條件。
20 {
21 scanf("%s",a[i].name);
22 if(!strcmp(a[i].name,"#"))
23
24 //利用這樣的判斷語句判斷輸入是否為#,(其實可以在判斷語句中實現賦值與判斷的共同實現,雖然好看,但是確保自己邏輯正確。)
25
26 //strcmp()比較兩個字符串是否相等,相等返還0,相等返還0,相等返還0.
27 break;
28 scanf("%s",a[i].tel);
29 i++;
30 n++;
31 }
32 return n;
33
34 //返回輸入結構體的數量。(一組數據就是一個結構體)
35 }
36 void search(struct aa *b,char *x,int n)
37
38 //x是一個指針,主函數中name是數組名,即一個指針。
39 {
40 int i;
41 i=0;
42 while(1)
43 {
44 if(!strcmp(b[i].name,x))
45 {
46 printf("name:%s tel:%s\n",b[i].name,b[i].tel);
47 break;
48 }
49 else
50 i++;
51 n--;
52
53 //其實這個n是用來計算是否所有結構體遍歷。但是我認為用i就行了。雖然i是在判斷語句裡面,但這個語句在沒找到目標是是一直執行的,如同在外面和n相同待遇
54
55 //當判斷語句找到目標時,那就更不需要他了。因為程序結束了。
56
57 if(n==0)
58 {
59 printf("No found!");
60 break;
61 }
62 }
63 }
64 main()
65 {
66 struct aa s[MAX];
67 int num;
68 char name[15];
69 num=readin(s);
70
71 //這個語句有兩個用處,1.將readin函數返回給num;2.readin函數將數據存儲於數組s。
72 printf("input the name:");
73 scanf("%s",name);
74 search(s,name,num);
75 }
反思:結構體在日後的使用中,就好像int、char這些數據類型一般,很常用。同時,這個程序中,我們學到程序中函數的簡單架構。另外,我們完全可以將這個函數擴展化。比如小到平時存儲東西,查找東西。大到各個程序中的存儲,查找功能等。
3.2鏈表
一般來說,數據結構這門課講解的第一個數據結構一般就是鏈表。很多教材第一頁就是鏈表。
曾經有公司高層談論編程時,說:”學生大多缺乏實際編程經驗,我在招聘時經常讓應聘者做一個簡單的鏈表。結果很多人都不會。“
鏈表可以說是數據結構應用的入門,如果連這個都不會,最好別去考慮與編程有關的職業。
曾經看過一本書,它的開頭講述了一個故事。說有一個剛入行的程序員第一次處理網站數據,做了一個1500長度的數組和循環用來存儲臨時數據。然後不久那個網站就掛了。後來作者去就將他做得數組改成了一個1500的循環鏈表就完事兒了。
鏈表是動態分配存儲空間來存儲數據,避免了空間的浪費。(當年用數組存儲數據有多少人和我一樣糾結空間大小的舉個手)同時,鏈表的存儲空間可以不連續、不連續、不連續。這樣產生了許多便利,並且有效利用了存儲空間。
我會講解每一個例子。(我才不會說,之前看的時候,我就記得單向鏈表了。。。)
實例078 創建單向鏈表
問題:創建一個單向鏈表,實現數據的輸入、輸出。
邏輯:鏈表是由一個個節點組成的。每一個節點分為兩部分:數據域與指針域。數據域用來存儲數據,指針域用來存儲下一個節點位置(雙向鏈表就還有一個指向前一個節點的鏈表)。一般第0個節點是整個鏈表的頭結點,稱為”頭指針“,一般不存放數據,只存放指向第1個節點的指針。鏈表的最後一個節點的指針設為空(NULL),作為鏈表的結束標志。
程序代碼:
1 #include<stdio.h>
2 #include<malloc.h>
3 //malloc是用來劃分存儲空間的函數。
4 struct LNode
5 {
6 int data;
7 struct LNode *next;
8 //指向下一個的結構體。從而形成鏈表。
9 };
10 struct LNode *create(int n)
11 {
12 int i;
13 struct LNode *head,*p1,*p2;
14 //head表示頭節點。p1,實現存儲空間的獲取,數據域賦值等。p2,作為與p1的中間指針,構成鏈表的替換連接。
15 int a;
16 head=NULL;
17 //頭節點為空。
18 printf("Input the integers:\n");
19 for(i=n;i>0;--i)
20 {
21 p1=(struct LNode*)malloc(sizeof(struct LNode));
22 //malloc(sizeof(struct LNode)表示劃分LNode大小的存儲空間。
23 //(struct LNode*)表示數據類型的轉換,將劃分來的存儲空間首地址轉化為LNode的變量名(指針類型)。
24 scanf("%d",&a);
25 p1->data=a;
26 //向p1的結構體中存儲數據。
27 if(head==NULL)
28 {
29 head=p1;
30 p2=p1;
31 //對頭節點的處理。
32 }
33 else
34 {
35 p2->next=p1;
36 //表明p2的下一個節點是p1。其中p2是上個循環中的p1。也就是說,p2只是一個中間變量temp。
37 p2=p1;
38 //印證了上個注釋中,p2的由來。
39 //具體的過程在變量名的說明中就已經體現了。
40 }
41 }
42 p2->next=NULL;
43 //最後一個節點的指針域為空,作為結束的標志。
44 return head;
45 //返回代表頭結點的結構體的變量名(指針)。
46 }
47 void main()
48 {
49 int n;
50 struct LNode *q;
51 printf("Input the count of the nodes you want to creat:");
52 scanf("%d",&n);
53 q=create(n);
54 //輸入,返回。
55 printf("The result is:\n");
56 while(q)
57 //q只有到了最後一個節點的next時,為NULL,才會跳出循環。一個很有用的小技巧。
58 {
59 printf("%d ",q->data);
60 q=q->next;
61 //實現q的轉換,從而輸出所有數據。
62 }
63 getch();
64 }
如果對其中提到的malloc函數,以及相關的calloc函數,free函數感興趣的話,可以浏覽http://blog.csdn.net/shuaishuai80/article/details/6140979。
反思:學習該例有這麼幾個要點: 1.指針不要混淆,不要將結構體名的指針和結構體內next的指針混淆,雖然有時這兩者表達的是同一個東西;
2.一定要完全理解p1=(struct LNode*)malloc(sizeof(struct LNode));這句代碼;(解釋在樣例中)
3.正確理解p2->next=p1;與p2=p1;這兩句代碼的實現。(無法理解可帶入數據進行兩到三次循環,即可理解)。
實例079 創建雙向鏈表
問題:創建一個雙向鏈表 ,並將這個鏈表中數據輸出到窗體上,輸入要查找的學生姓名,將查找的姓名從鏈表中刪除,並現實刪除後的鏈表。
邏輯:其實就邏輯而言是很簡單的。與之前的單向鏈表相同,不過比後繼結點的設置多了一個前驅節點的設置。另外查找,與一般的結構體數組查找,並沒有什麼區別。至於刪除嘛,就需要改動刪除節點的前驅節點和後繼結點的設置。具體操作,可以看代碼。
程序代碼:
1 #include<stdio.h>
2 typedef struct node
3 {
4 char name[20];
5 struct *prior,*next;
6 //設立節點的前驅節點和後繼結點。
7 }stud;
8 stud *creat(int n)
9 {
10 stud *p,*h,*s;
11 int i;
12 h=(stud*)malloc(sizeof(stud));
13 //申請存儲空間
14 h->name[0]='\0';
15 //設置name為空
16 h->prior=NULL;
17 h->next=NULL;
18 p=h;
19 for(i=0;i<n;i++)
20 {
21 s=(stud*)malloc(sizeof(stud));
22 p->next=s;
23 printf("Input the %d student'sname:",i+1);
24 scanf("%s",s->name);
25 s->prior=p;
26 s->next=NULL;
27 p=s;
28 //方法和單向鏈表沒什麼區別,區別只在於多了一個前驅節點的設置。
29 //從一些方面來說,多了前驅節點更容易理解了。
30 }
31 p->next=NULL;
32 //設置最後節點的後繼結點為空,作為結束標志。
33 return(h);
34 }
35 stud *search(stud *h,char *x)
36 {
37 stud *p;
38 char *y;
39 p=h->next;
40 while(p)
41 {
42 y=p->name;
43 if(strcmp(y,x)==0)
44 //判斷是否為尋找的目標。
45 return(p);
46 //返回目標地址。
47 else
48 p=p->next;
49 //繼續下一個節點的檢測。
50 }
51 printf("cannot find data!\n");
52 //沒有任何符合條件的返回。
53 }
54 void del(stud *p)
55 {
56 p->next->prior=p->prior;
57 //令p的下一個節點的前驅節點與現在p的前驅節點一致(這樣在前驅節點中p就不存在了。並且鏈表沒有斷開。)
58 p->prior->next=p->next;
59 //令p的前一個節點的後繼結點與現在p的後繼結點一致(這樣在後街節點中p就不存在了。並且鏈表沒有斷開。)
60 //切記:p->next是指p的後一個節點。p->prior同理。
61 //無法理解的話,請在草稿紙上畫出鏈表圖,就清晰無比了。
62 //這裡兩句代碼在有些運行環境中會報錯,可以自行更改語句或環境。
63 free(p);
64 //釋放p的存儲空間(鏈表上p已經不存在了。當然不能讓它占著空間了)
65 }
66 main()
67 {
68 int number;
69 char sname[20];
70 stud *head,*sp;
71 puts("Please input the size of the list:");
72 scanf("%d",&number);
73 head=creat(number);
74 sp=head->next;
75 printf("\nNow the double list is:\n");
76 while(sp)
77 {
78 printf("%s",&*(sp->name));
79 sp=sp->next;
80 }
81 //之前的都與單向鏈表相同。
82 printf("\nPlease input the name which you want to find:\n");
83 scanf("%s",sname);
84 sp=search(head,sname);
85 //通過search函數,尋找到所要尋找的sname的地址sp。
86 printf("the name you want to find is:\n",*&sp->name);
87 del(sp);
88 //將sp帶入到del()函數中,刪除。
89 sp=head->next;
90 //這句話只是為了下面的while循環做二次利用的。完全可以刪除這句話,為下面的循環單獨設置一個變量。
91 printf("\nNow the double list is:\n");
92 while(sp)
93 {
94 printf("%s",&*(sp->name));
95 sp=sp->next;
96 }
97 printf("\n");
98 puts("\nPress any key to quit...");
99 getch();
100 //與單向鏈表相同的輸出原理。
101 }
反思:雙向鏈表與單向鏈表並沒有什麼不同。要記得的是,雙向鏈表可是比單向鏈表多了一個prior的指針,無論是添加,刪除,移動,修改,都要記住。
實例080 循環鏈表
還記得開頭,我說的那個網站關於鏈表的故事嗎。這下說的就是循環鏈表。
其實到了這個時候也就沒有什麼說的了。循環鏈表就是將最後節點的後繼結點設置為頭結點。
循環鏈表與普通鏈表的操作基本一致,只是在算法中循環遍歷鏈表節點時判斷條件不再是p->next是否為空,而是是否等於鏈表的頭結點
程序代碼:(這裡就不演示。篇幅不應該留給不需要的東西。)
實例081 雙鏈表逆置
問題:創建一個雙向鏈表,將雙向鏈表的節點逆置,即將尾節點放到第一個節點的位置,倒數第二個節點放到第二個節點的位置,依此類推。
邏輯:雙向鏈表的創建、輸入、輸出,都和之前一樣。為了模塊化的操作,就單獨創建一個reverse函數,用來執行逆置操作。逆置操作就是指針的變換。
部分代碼:
1 stud *reverse(stud *head)
2 {
3 stud *p,*r,*h;
4 h=head->next;
5
6 //設置h。
7 if(h&&h->next)
8 {
9 p=h;
10 r=p->next;
11 p->next=NULL;
12 while(r)
13 {
14 p=r;
15 r=r->next;
16 p->next=h;
17 h->prior=p;
18 h=p;
19
20 //中間變量p,變量r從鏈表的頭部向後遍歷,變量h從鏈表的尾部向前遍歷。變量p作為中間變量來轉移指針地址,完成鏈表逆置。
21 }
22 head->next=h;
23 h->prior=head;
24
25 //事後,完成鏈表的補全。即鏈表的開頭和結尾。
26 return head;
27 }
28 }
反思:想要完成這些,自己一定不能混淆指針,指針的指針,指針的指向。這三個概念。
後面還有很多相關的知識,比如逆序輸出,約瑟夫環,鏈表的元素插入,節點插入,節點刪除,合並鏈表以及頭插入法建立鏈表等。但是,我認為,如果之前的鏈表都懂了。那麼後面的知識無非就是鏈表的基礎知識版應用。如果有需要,可以找我。
總結:其實鏈表在學習後,就會發現,之前指針學得好是多麼重要啊。尤其是指針的指針這一點。如果,指針學得好,那麼鏈表要學的就是鏈表的概念,鏈表的一些專用函數,以及常用的算法。那麼之後的一些應用就是水到渠成的事兒。(完全就是之前程序的鏈表版應用啦。)
由於時間關系,這次就先寫這麼多了。(其實這次寫的比之前多了很多,尤其線下還做了那麼多的程序調試。)
謝謝大家的鑒賞和評價。也希望能夠結識一些相關興趣的小伙伴。
另外,也許不久,我還會寫一些別的東西。
(剛剛博客園官方通知了我,我才知道有代碼插入這個東西。挺贊的。對於我這個有代碼行潔癖的,每次copy後敲Tab,也是蠻難過的。謝謝了。)