链表与状态机

原创 2016年08月30日 22:35:15

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语言专题已结束。。继续加油吧。。也感谢朱老师的教程,很好懂,但是毕竟因为只能算入门级了。

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

对个geek青年的状态机,查表纯C实现的代码修改

对个geek青年的状态机,查表纯C实现的代码修改感谢杨福贵老师无私的开源精神,原文出处 http://blog.csdn.net/younggift/article/details/35848677...

geek青年的状态机,查表,纯C语言实现

用查表法实现状态机引擎,支持状态迁移和状态-动作对应。查表法的优点是,代码稳定,不随状态、状态迁移匹配条件、动作的增加而变化。...

C/C++用状态转移表联合函数指针数组实现状态机FSM

状态机在工程中使用非常的频繁,有如下常见的三种实现方法: 1. `switch-case` 实现,适合简单的状态机; 2. 二维状态表`state-event`实现,逻辑清晰,但是矩阵通常比较稀疏,而...

基于状态机的简易RISC CPU设计

  • 2017年11月01日 20:55
  • 735KB
  • 下载

状态机与编程以及函数指针数组的妙用

转自:http://kb.cnblogs.com/page/528971/   状态机的概念   状态机是软件编程中的一个重要概念,比这个概念更重要的是对它的灵活应用。在一个思路清晰而且高效的...
  • zjzto
  • zjzto
  • 2016年07月01日 11:10
  • 1117

有限状态机实例

  • 2014年11月05日 19:13
  • 273KB
  • 下载

状态机在单片机程序设计中的应用

  • 2015年08月11日 11:16
  • 6.22MB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:链表与状态机
举报原因:
原因补充:

(最多只允许输入30个字)