鏈表與狀態機


1.單向鏈表概念以及相應操作:
(1).基本概念:
結構體允許成員類型不相同,解決數組第一個缺陷。鏈表允許大小可變,解決數組第二個缺陷。(該缺點三個解決思路:拆遷,搬遷(c++/java支持),外部擴展(鏈表))。

鏈表就是用來存儲數據的。鏈表用來存數據相對於數組來說優點就是靈活性,需要多少個動態分配多少個,不佔用額外的內存。數組的優勢是使用簡單(簡單粗暴)。

鏈表是由節點組成的,節點中包含:有效數據和指針。指針指向下一節點或者NULL。注意->訪問成員變量,pNext纔是用來連接的指針。

頭指針並不是節點,而是一個普通指針,只佔4字節。頭指針的類型是struct node *類型的,所以它才能指向鏈表的節點。
注意有的鏈表是有頭結點的,和頭指針一起定義;他的有效數據存節點個數或者爲空。

(2).插入:

********在插入一個節點時注意如果新節點先和前面的連接,就無法連接到後續節點了;所以需要先連接後面的結點再連接前一個節點。

(3).遍歷:遍歷是操作順序非常重要。
*********while(NULL !=p->pNext)
{
p=p->pNext;
p->data......
}
注意用這樣遍歷會略過第一個節點(頭節點),最後p指向最後一個節點。

*********while(NULL !=p->pNext)
{
p->data......
p=p->pNext;
}
注意用這樣遍歷不會略過第一個節點(頭節點),會漏掉最後一個節點。

*********while(NULL !=p)
{
   p=p->pNext;
p->data......
}
注意用這樣是錯誤用法。


(4).刪除:
*********在找到你要刪除的那個節點後,你就訪問不到他的前一個節點,就不能把他的前一個和後一個連接起來了;此時就需要一個pPrev指針,指向p指向的前一個節點。(在循環內第一句加上pPrev=p即可)

(5).逆序:

採用遍歷+頭插入。把鏈表分爲兩部分:保留一個頭結點和第一個有效節點,用另一個指針指向第二個有效節點依次遍歷並頭插入第一部分(頭結點後,原第一有效節電前)。

#include<stdio.h>
#include<stdlib.h>

 struct node 
{
	int data;
	struct node * pNext;
};



struct node * creat_node(int data);
void insert_header(struct node *pH,struct node *new);
void insert_tail(struct node *pH,struct node *new);
void link_list_delete(struct node *pH,int data);
void link_list_traverse(struct node *pH);
void link_list_reverse(struct node *pH);




int main(int argc,char **argv[])
{
	struct node *pHeader=creat_node(0);
	insert_tail(pHeader,creat_node(11));
	insert_tail(pHeader,creat_node(22));
	insert_tail(pHeader,creat_node(33));
	
	insert_header(pHeader,creat_node(12));
	insert_header(pHeader,creat_node(13));
	insert_header(pHeader,creat_node(14));
	//printf("pHeader->pNext->data is %d\n",pHeader->pNext->data);
	link_list_traverse(pHeader);
	link_list_delete(pHeader,0);
	
	link_list_reverse(pHeader);
	link_list_traverse(pHeader);
	
	
	return 0;
}

struct node * creat_node(int data)
{
	struct node *p=(struct node *)malloc(sizeof(struct node));
	if(NULL == p)
	{
		printf("malloc erro");
		return NULL;
	}
	p->data=data;
	p->pNext=NULL;
	return p;
}

void insert_header(struct node *pH,struct node *new)     //一定要記的pNext纔是用來連接的,->是用來訪問結構體成員的。
{
	struct node *p=pH;                                   
	new->pNext=p->pNext;                                 //注意在頭部插入需要新節點先和後面的連接,再和前面的連接;要不然先連接前面就訪問不到後面的節點了
	p->pNext=new;
	pH->data++;
}

void insert_tail(struct node *pH,struct node *new)
{
	struct node *p=pH;
	while(NULL !=p->pNext)                           //這樣寫也避免了下面的錯誤。
	{
		p=p->pNext;
		pH->data++;
	}                                              
	p->pNext=new;
	new->pNext=NULL;

}

void link_list_traverse(struct node *pH)
{
	struct node *p=pH;
	printf("there is %d nodes:\n",pH->data);
	
/*
	while(NULL !=p)                                      //這樣寫,在倒數第二次循環中,當p指向最後一個節點是不爲空;最後一次循環p就是NULL了,data也就沒有了
	{
		p=p->pNext;
		printf("the data is %d\n",p->data);
	}
*/	

	while(NULL !=p->pNext)
	{
		p=p->pNext;                                      //先前進一步再打印避免打出頭結點。
		printf("the data is %d\n",p->data);
	}                                                    //這裏體現了有頭結點的好處,在這裏這樣寫起來很工整。
		
}

void link_list_delete(struct node *pH,int data)
{
	struct node *p,*pPrev;
	p=pH;
	pPrev=NULL;
	int check_find=0;                                          //把沒找到節點和刪完了節點兩種情況區分開。
	while(NULL !=p->pNext)
	{
		pPrev=p;
		p=p->pNext;
		if(p->data ==data)
		{    
			if(NULL == p->pNext)
			{
				pPrev->pNext=NULL;
				free(p);                                        //這樣是可以刪除多個data相同的節點,要是隻想刪第一個,加一個return;或break;.
			}
			else{
				pPrev->pNext=p->pNext;
				free(p);
			}
		printf("delete successfully\n");
		pH->data--;
		check_find=1;
		}
	}
	if(check_find==0)
	printf("delete erro ,check if there is the data \n");
}

void link_list_reverse(struct node *pH)
{
	
	struct node *p,*pHold;
	p=pH->pNext;
	pHold=p;
	
	if(NULL ==p->pNext || NULL == p)
		return;
	
	while(NULL !=p->pNext)
	{
		pHold=pHold->pNext;
		if(p==pH->pNext)
			p->pNext=NULL; 
		else 
		{	
			p->pNext=pH->pNext;
			pH->pNext=p;
		}
	p=pHold;                                    
	}
insert_header(pH,p);//當循環結束時,p指向最後一個節點,還沒出插入前一部分呢。以前都是在循環內先前進一個再處理,所以不會漏掉最後一個,但漏掉第一個。這裏先處理,再前進不會漏第一個,但會漏最後一個。    

}


2.雙向鏈表概念及操作:
(1).基本概念:
雙向鏈表的節點 = 有效數據 + 2個指針(一個指向後一個節點,另一個指向前一個節點)
單向鏈表只能由頭指針往後訪問後續節點,雙鏈表就更靈活可以左右移動。

(2).插入:頭部插入,尾部插入:注意還是應該先處理新節點和後續節點的連接,再處理新節點和前節點的連接。
(3).遍歷:
(4).刪除:這裏體現了雙向鏈表的優點,不需要再額外設置一個前向指針。
#include<stdio.h>
#include<stdlib.h>


struct node {
	int data;
	struct node * pNext;
	struct node * pPrev;
};


struct node *create_node(int data);
void insert_header(struct node *pH,struct node *new);
void insert_tail(struct node *pH,struct node *new);
void traverse_forward(struct node *pH);
void traverse_backward(struct node *pT);
int  node_delete(struct node *pH,int data);

int main(void)
{
	struct node *pHeader=create_node(0);
	printf("pHeader->data is %d\n",pHeader->data);
	insert_header(pHeader,create_node(23));
	insert_header(pHeader,create_node(34));
	insert_header(pHeader,create_node(45));
//	printf("debug\n:");
	
/*	printf("the node data is %d :\n",pHeader->pNext->data);
	printf("the node data is %d :\n",pHeader->pNext->pNext->data);
	printf("the node data is %d :\n",pHeader->pNext->pNext->pNext->data);
*/	
//	traverse_forward(pHeader);

	struct node *	p1=pHeader->pNext->pNext->pNext;
	traverse_backward(p1);
	node_delete(pHeader,34);
	traverse_backward(p1);
	return 0;
}



struct node * create_node(int data)
{
	struct node *p=(struct node *)malloc(sizeof(struct node));
	if(NULL == p)
	{
		printf("malloc erro\n");
		return NULL;
	}
	p->data=data;
	p->pNext=NULL;
	p->pPrev=NULL;
	return p;
}



void insert_header(struct node *pH,struct node *new)
{	
	if(NULL ==pH->pNext)
	{
		pH->pNext=new;
		new->pPrev=pH;
	}
	new->pNext=pH->pNext;              //先鏈接後面的
	pH->pNext->pPrev=new;
	
	new->pPrev=pH;
	pH->pNext=new;                    //再連接前面的
	
	pH->data++;
	
}



void insert_tail(struct node *pH,struct node *new)
{
	struct node *p=pH;
	while(NULL !=p->pNext)
	{
		p=p->pNext;
	}
	pH->data++;
	p->pNext=new;
	new->pPrev=p;
}


void traverse_forward(struct node *pH)
{
	struct node *p=pH;
	while(NULL !=p->pNext)
	{
		p=p->pNext;                                     
		printf("the data is %d\n",p->data);
	}
	
}

void traverse_backward(struct node *pT)
{
	struct node *p=pT;
	while(NULL !=p->pPrev)
	{
		printf("the node data is %d\n",p->data);
		p=p->pPrev;
	}
}

int  node_delete(struct node *pH,int data)
{
	struct node *p=pH;
	while(NULL !=p->pNext)
	{
		p=p->pNext;
		if(p->data ==data)
		{
			if(NULL == p->pNext)
			{
				p->pPrev->pNext=NULL;
				                              //這了本該有p->pPrev置爲NULL,但是以後free就不用了。但是要有這種想法!!!!
			}
			else{
				p->pNext->pPrev=p->pPrev;
				p->pPrev->pNext=p->pNext;
			}
			
			free(p);
			return 0;
		}
		
	}
	printf("find erro\n");
		return -1;
}





3.linux內核中的鏈表:
(1).實現在include/linux/list.h中,list.h內是一個單純的鏈表封裝,包括節點定義以及相應操作。(函數前有下劃線是說一般不要被別人使用)
(2).在使用鏈表時,我們是把一個鏈表的實例作爲結構體的一個成員,結構體內還有其他的有效信息,以此實現真正的鏈表。
(3).用內核鏈表時實際上通過container_of宏,由結構體成員來實現整個結構體的操作。



4.狀態機:
(1).一般的狀態機是FSM(有限),這個機器會就收外部的信號和輸入,再根據自己的狀態和用戶輸入跳轉到另一狀態。
(2).狀態機分爲
MOORE型:機器狀態只與自己的當前狀態有關(比如當有外部輸入時,就跳到另一狀態)。
MEARLY型:機器狀態和用戶輸入與當前狀態相關。
(3).狀態機的主要用途:電路設計、FPGA程序設計、軟件設計
電路設計中廣泛使用了狀態機思想
FPGA程序設計(就是設計芯片的,比如那些搞時序的)
軟件設計(框架類型的設計,譬如操作系統的GUI系統、消息機制)


(4).狀態機解決了什麼問題
我們平時寫程序都是順序執行的,這種程序有個特點:程序的大體執行流程是既定的,程序的執行是遵照一定的大的方向有跡可尋的。但是偶爾會碰到這樣的程序:外部不一定會按照既定流程來給程序輸入信息,而程序還需要完全能夠接收並響應外部的這些輸入信號,還要能做出符合邏輯的輸出。


C語言實現簡單的狀態機:當自己輸入的密碼爲201417時開鎖,若其中有輸錯則返回初始狀態重新輸入。

#include<stdio.h>


typedef enum{
	 STATE0,
	 STATE1,
	 STATE2,
	 STATE3,
	 STATE4,
	 STATE5,
	 STATE6,
}state;


int main(void)
{
	state current_state=STATE0;
	int password=0;
	while(1)
	{
		
		if(current_state==STATE0)
		printf("please enter your password from the beginning:\n");
		scanf("%d",&password);
		switch(current_state)
		{
			case STATE0:
				if(password==2)
					current_state=STATE1;
				else
					current_state=STATE0;
				break;
			case STATE1:
				if(password==0)
					current_state=STATE2;
				else
					current_state=STATE0;
				break;
			case STATE2:
				if(password==1)
					current_state=STATE3;
				else
					current_state=STATE0;
				break;
			case STATE3:
				if(password==4)
					current_state=STATE4;
				else
					current_state=STATE0;
				break;
			case STATE4:
				if(password==1)
					current_state=STATE5;
				else
					current_state=STATE0;
				break;
			case STATE5:
				if(password==7)
				{
					current_state=STATE6;
					printf("the lock is unlocked");
				}
					
				else
					current_state=STATE0;
				break;
			default:
				printf("i don't what you are doing,now the lock is locked\n");
				current_state=STATE0;
				break;
		}	
			if(current_state==STATE6)
				break;
	}
	
	return 0;
}





到此,C語言專題已結束。。繼續加油吧。。也感謝朱老師的教程,很好懂,但是畢竟因爲只能算入門級了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章