华清远见嵌入式学习每周汇总

第一周数据结构

Makefile的编写

搞明白Makefile是什么之前,先来了解一下make工具
1.make是工程管理器,顾名思义,是指管理较多的文件 Make工程管理器也就是个“自动编译管理器”,这里的“自动”是指它能构根据文件 时间戳 自动发现更新过的文件而 减少编译的工作量 ,同时,它通过读入Makefile文件中文件的内容来执行大量的编译工作。

2.make工具的作用当项目中包含多个c文件,但只对其中1个文件进行了修改,那用gcc编译会将所有的文件从头编译一遍,这样效率会非常低;所以通过make工具,可以查找到修改过的文件(通过时间戳),只对修改过的文件进行编译,这样大大减少了编译时间,提高编译效率

3.Makefile是Make读入的唯一配置文件(有的编译器支持makefile也作为make读入的配置文件,但哥们才疏学浅不能保证哪个编译器支持,就统一Makefile得嘞。)
4.Makefile的编写格式

   格式:
   目标:依赖
   		命令

比如使用make工具编译add.c main.c生成可执行文件add的Makefile命令如下

add:add.o main.o//最终生成可执行文件add 最终目标要写在第一行
	gcc add.o main.o -o add
add.o:add.c
    gcc -c add.c -o add.o
main.o:main.c
	gcc -c main.c -o main.o

但是如若是这样的格式,我们需要写在Makefile中的文件还是太多了,尤其是一个add.o要写三遍,那么后续几十个文件的程序单单写个Makefie不得小5分钟?make工具创作者当然也是想到了这样的情况,于是有了以下的通配符和变量的应用。

CC=gcc
CFLAGS=-g -c -Wall -O
OBJS=add.o main.o
add:$(OBJS)
	$(CC) $(OBJS) -o $@
%.o:%.c
	$(CC) $(CFLAGS) $^ -o $@
PHONY:clean
clean:
	rm *.o add #编译过程中生成的.o文件和最终生成的可执行程序add!

接下来在终端输入make指令就可以生成可执行文件了。这样子看确实是比上面的内容还要多,但这中算是一劳永逸的办法,后面如果编译过程一致的话,只需要改一下OBJS变量的值和最终生成的可执行文件名字就可以了^^
如果这些符号不理解的话可以参考一下带佬整理的这篇文章,你想知道的Makefile内容都有囊括!上链接!:支持原创

顺序表

顺序表是数据结构的开场白,因为它就是数组,便于理解引入的表的概念。
逻辑结构:线性结构
存储结构:顺序存储
特点:内存空间连续开辟
头节点无前驱,尾节点无后继

顺序表操作:

  • 创建空列表
  • 增删改查

先来看看Makefile

CC=gcc
CFLAGS=-g -c -Wall -O
OBJS=main.o seqlist.o
seqlist:$(OBJS)
	$(CC) $(OBJS) -o $@
PHONY:clean
clean:
	rm *.o seqlist

在make之前一定要在main.c里先创建主函数,没有主函数是没有程序入口的,生成不了可执行文件。
再来看看头文件seqlist.h

#ifndef _SEQLIST_
#define _SEQLIST_
#include<stdio.h>
#include<stdlib.h>//malloc free
#define N 10//后续若需要改变顺序表的大小 只需改动此处的10即可
typedef int datatype;//后续若需要改动输入数据的数据类型 只需要改动此处的int即可
typedef struct
{
	datatype data[N];
	int last;
}seqlist_t;//定义并重命名结构体为seqlist_t
seqlist_t *Createseqlist(void);//创建顺序表
int insert(seqlist_t* ,int,datatype);//插入表
int seqlist_full(seqlist_t*);//判断表是否为满
int Delete(seqlist_t* ,int);//删除表
int seqlist_empty(seqlist_t*);//判断表是否为空
void show_seqlist(seqlist_t*);//遍历展示
int Modvalue_position(seqlist_t* ,int,int);//修改指定位置的值
int checkvalue_position(seqlist_t* ,int);//查看指定位置的值
int Modvalue_value( seqlist_t* ,int,int);//修改与给出值一致的值
int checkposition_value(seqlist_t* ,int);//查看与给出值一直的值的位置
int cleanseqlist(seqlist_t* );//清空表
void Destroyseqlist(seqlist_t* );//销毁表
#endif

注意:如果在h文件中定义了全局变量,一个c文件包含同一个h文件多次,如果不加#ifndef宏定义,会出现变量重复定义的错误,头文件开头结尾分别添加#ifndef #define和#endif就可以避免这种错误。

下面看一下各个函数的作用吧

seqlist_t *Createseqlist(){
	seqlist_t *p;
	p=(seqlist_t * )malloc(sizeof(seqlist_t));
	if(p == NULL)//申请空间失败
		{
			printf ( "Createseqlist failed ! \n " );
			return NULL;
		}
	p->last=-1;//初始化空的顺序表
	return p;
	}


这个last并不是指针,他只是在这里代表目前整个链表的最后一个节点的位置。0~9这里指的也是此节点的位置,每个节点的内容目前还没有赋值

int Insert(seqlist_t *p,int post,datatype x)
{
	if(post>p->last+1||post<0||Seqlist_full(p))
	{
		printf("SequenceList is full\n");
		return -1;
	}
	int i=0;
	for(i=p->last;i>=post;i--)
	{
		p->data[i+1]=p->data[i];
	}
	p->data[post]=x;
	p->last++;//每次插入后last都要后移一个,保证last指向的是最后一个元素
	return 0;
}

插入数据的时候只能在last位置的下一个位置插入,就像数组一样,不可以4号位置有元素6号位置有元素而5号位置没有,这样就不符合数组的连续存储原则。

int Delete(seqlist_t* p,int post)//删除
{
	if(p->last<post||post<0||Seqlist_empty(p))
	{
		printf("Can not delete element here\n");
		return -1;
	}
	int i=0;
	for(i=post;i<p->last;i++)
	{
		p->data[i]=p->data[i+1];
	}
	p->last --;
}
int Seqlist_empty(seqlist_t* p)//判断是否为空
{
	return p->last == -1;
}
void Show_seqlist(seqlist_t* p)//遍历输出
{
	int i=0;
	for(i=0;i<=p->last;i++)
	{
		printf("%d ",p->data[i]);
	}
	printf("\n");
}
int Modvalue_position(seqlist_t* p,int post,int value)//按照位置修改值
{
	p->data[post]=value;
	return 0;
}
int Modvalue_value(seqlist_t* p,int a,int b)//按照值修改值
{
	int i=0;
	for(i=0;i<p->last;i++)
	{
		if(p->data[i]==a)
			p->data[i]==b;
	}
	return 0;
}
int Checkvalue_position(seqlist_t* p,int post)//查看指定位置的值
{
		printf("%d\n",p->data[post]);
}
int Checkposition_value(seqlist_t* p,int value)//查看指定值的位置,若有多个相同值,均输出
{
	if(Seqlist_empty(p))
		return -1;
	else
		{
			int i;
			for(i=0;i<p->last;i++)
			{
				if(value==p->data[i])
				printf("a[%d]=%d ",i,value);
			}
		}
	return 0;
}
int Cleanseqlist(seqlist_t* p)//清空顺序表
{
	p->last=-1;
	return 0;
}
void Destroyseqlist(seqlist_t* p)//销毁顺序表
{
	free(p);
	p=NULL;//避免再调用到p时出现段错误(野指针)
}

清空顺序表只需要将作为标志的last置为-1即可,因为我们访问操作都是截止到last为止,所以后面申请的N个元素就可以视为无效元素,虽然没有调用free函数释放,但是我们就可以当他们是空的

链表(含约瑟夫环之选猴王)

#ifndef _LINKLIST_H_
#define _LINKLIST_H_
#include<stdio.h>
#include<stdlib.h>

typedef int datatype;
typedef struct node_t
{
	datatype data;//data area
	struct node_t * next;//point area
}linklist_t;
//create empty head_linklist
linklist_t *CreateEmptyLinklist(void);
int Calc_length(linklist_t * p);
int Initialize_linklist(linklist_t *p,int num);
int Insert_position(linklist_t *p,int post,int value);
void Show_linklist(linklist_t *p);
int Delete_position(linklist_t *p,int post);
int Mod_position(linklist_t *p,int post,int value);
int Linklist_empty(linklist_t* p);
int Read_position(linklist_t *p,int post);
int Read_value(linklist_t *p,int value);
int Delete_value(linklist_t *p,int value);
int Linklist_inversion(linklist_t *p);
int Linklist_clean(linklist_t *p);
int Linklist_destroy(linklist_t **p);

linklist_t* Linklist_merge(linklist_t* p1,linklist_t* p2);

//Joseph
int createJoseph(linklist_t *p,int num);
int chooseMonkeyking(linklist_t* p,int start,int kill);
#endif

先来看看结构体,每个linklist_t类型的变量都有一个自己的数据域存储本节点的数据和指向下一个linklist变量的指针域,这样前指后就形成了一个链表。
链表

#include"linklist.h"
linklist_t *CreateEmptyLinklist(void)
{
	linklist_t* p=(linklist_t *)malloc(sizeof(linklist_t));
	if(p == NULL){
		printf("malloc failed\n");
		return NULL;
	}
	p->data=0;
	p->next=NULL;
	return p;
}

新建链表时需要先创建头节点,后续的所有操作都是在头节点的基础上进行的,头节点是有数据域的,但是我们人为的认定这个数据与不被访问,所以在此赋值一个0作为“标记”。

int Initialize_linklist(linklist_t *p,int num)//“初始化”的头节点的个数,
						//	其实就是插入节点的个数,初始化之后也能再次“初始化”。
{
	int i;
	linklist_t *t=p;//用于存储链表头指针的位置,防止后续操作完初始化,指针指到最后的节点
					//找不到头节点而无法访问此链表
	for(i=0;i<num;i++){
		printf("The %d num is: \n",i+1);
		p->next=(linklist_t*)malloc(sizeof(linklist_t));
		scanf("%d",&(p->next->data));
		p=p->next;//每初始化成功一个节点,头节点就指向后面的节点,方便后续的插入
	}
	p=t;//操作完成后再指向原本的头
	return 0;
}

下面看一下初始化的过程
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

int Calc_length(linklist_t * p)//计算链表长度(不含头指针)
{
	int length=0;
	while(p->next!=NULL)
	{
		length++;
		p=p->next;
	}
	return length;
}
int Insert_position(linklist_t *p,int post,int value)//按照位置插入
{
	if(post<0||post>Calc_length(p))
	{
		printf("position error\n");
		return -1;
	}
	while(post--!=0)
	{
		p=p->next;
	}
	linklist_t * t=CreateEmptyLinklist();
	t->data=value;
	t->next=p->next;
	p->next=t;
	printf("Insert value by position success!\n");
	return 0;
}
void Show_linklist(linklist_t *p)//遍历输出
{
	while(p->next!=NULL)
	{
		p=p->next;
		printf("%d ",p->data);
	}
	printf("\n");	
}

删除操作的时候需要注意遍历到删除位置的前一个节点后,不能直接t->next=t->next->next跳过它,因为这样,虽然是访问不到删除的节点了,但是它却仍然存在,占用着内存,会导致内存泄漏,所以我们需要定义一个指向删除节点的linklist_t类型的指针,用于在"删除"操作后进行释放内存空间。释放完成后也要记得tp=NULL,防止产生野指针。
在这里插入图片描述
在这里插入图片描述

int Delete_position(linklist_t *p,int post)//按照位置删除
{
	if(post<0||post>Calc_length(p)||Calc_length(p)==0)
	{
		printf("position error\n");
		return -1;
	}
	linklist_t *tp=p;//存储p的位置 移动tp进行操作即可,不要动原来的链表头节点
	while(post--!=0)
	{
		tp=tp->next;
	}
	linklist_t *t;
	t=tp->next;
	tp->next=tp->next->next;
	free(t);
	t=NULL;
	return 0;
}

下面的函数功能都离不开遍历整个链表,对比对应的位置或者值,然后进行具体操作,重点是理解怎么遍历到这个节点的。

int Mod_position(linklist_t *p,int post,int value)
{
	if(post<0||post>Calc_length(p)||Calc_length(p)==0)
	{
		printf("position error\n");
		return -1;
	}
	while(post--!=0)
	{
		p=p->next;
	}
	p->next->data=value;
	printf("Mod_position success!\n");
	return 0;
}
int Linklist_empty(linklist_t *p)
{
	if(Calc_length(p)==0)
	{
		printf("This linklist is empty\n");
		return -1;
	}
	return 0;
}
int Read_position(linklist_t* p,int post)
{
	if(post<0||post>Calc_length(p))
	{
		printf("position error\n");
		return -1;
	}
	int t=post;
	while(t--!=0)
	{
		p=p->next;
	}
	printf("In position %d,the value is %d\n",post,p->next->data);
	return 0;
}
int Read_value(linklist_t *p,int value)
{
	int t=0;
	while(p->next!=NULL)
	{
		p=p->next;
		if(p->data==value)
			printf("position%d's value is %d\n",t,value);
		t++;
	}

	return 0;
}
int Delete_value(linklist_t *p,int value)
{
	if(Linklist_empty(p))
	{
		printf("linklist is empty,PLZ initialize fist\n");
		return -1;
	}
	int i=0;
	linklist_t *t=p;
	while(t->next!=NULL)
	{
		t=t->next;
		if(t->data==value)
		{
			//Delete
			Delete_position(p,i);
			i--;
		}
		i++;
	}
	printf("Delete data by value succeed!\n");
	return 0;
}

这里链表的倒置(1 2 3 4 5倒置后为5 4 3 2 1)用到的是头插法,思路如下
定义一个指针p指向头节点的下一个节点,然后再定义一个指针q(用于保存下一个操作的节点),断开头节点(这之前是前期准备),q指向p,让q后移一个,然后改变指针p的next的指向,从q指向头节点的next(头节点断开了,此时为NULL),头节点此时next指向p,p再指向q实现后续的循环。这样p每次都能为头节点贡献一个next指向的新节点,然后再指向q的节点重复。直到全部遍历晚后,原来的1 2 3 4 5因为1是第一个被头节点next指向的,跑到了最后,反而最后被p贡献的5成了头节点next第一个指向的。如此就实现了链表的倒置。

int Linklist_inversion(linklist_t *p)
{
	linklist_t* i=p->next;
	p->next=NULL;
	linklist_t *j;
	while(i!=NULL)
	{
		j=i;
		i=i->next;
		j->next=p->next;
		p->next=j;
	}
	printf("transform succeed\n");
	return 0;
}

若不理解请看带佬的还有递归法倒置

int Linklist_clean(linklist_t *p)
{
	linklist_t *t=CreateEmptyLinklist();
	while(p->next!=NULL)
	{
		t->next=p->next;
		p->next=p->next->next;
		free(t->next);
		t->next=NULL;
	}
	free(t);
	t=NULL;
	printf("linklist is cleaned\n");
	return 0;
}
int Linklist_destroy(linklist_t **p)
{
	Linklist_clean(*p);
	free(*p);
	*p=NULL;
	printf("linklist is destoried\n");
	return 0;
}
linklist_t * Linklist_merge(linklist_t * p1,linklist_t* p2)//有序链表的合并
{
	linklist_t *L=CreateEmptyLinklist();
	//	p1=p1->next;
	//	p2=p2->next;
	linklist_t *t1=p1;
	linklist_t *t2=p2;
	linklist_t *t = L;
	t1=t1->next;
	t2=t2->next;
	if(t1->data>t2->data)
	{
		L->next=t2;
		t2=t2->next;
		L=L->next;
		L->next=NULL;
	}
	if(t1->data<t2->data)
	{
		L->next=t1;
		t1=t1->next;
		L=L->next;
		L->next=NULL;
	}
	while(t1!=NULL&&t2!=NULL)
	{
		if(t1->data<t2->data)
		{
			L->next=t1;
			//	if(t1->next!=NULL)
			t1=t1->next;
			L=L->next;
			L->next=NULL;
		}
		else{
			L->next=t2;
			t2=t2->next;
			L=L->next;
			L->next=NULL;
		}
	}
	if(t1)//若循环后 第一个参数的链表没有遍历完,后续的有序链表就是第一个参数的链表了,直接整体接上
		L->next=t1;
	if(t2)//若循环后 第二个参数的链表没有遍历完,后续的有序链表就是第一个参数的链表了,直接整体接上
		L->next=t2;
	L=t;
	return L;
}
Joseph circle

下面就是单项循环列表也就是Joseph circle了,其本质还是链表,只不过在链表最后的那个节点的next不指向NULL了,而是再指向头节点。//这样就可以一直循环起来了(不是)。

int createJoseph(linklist_t *p,int num)
{
	linklist_t *t=p;
	int i;
	for(i=1;i<num;i++)
	{
		t->next=CreateEmptyLinklist();
		t=t->next;
		t->data=i;
	}
	t->next=p;
	return 0;
}
int chooseMonkeyking(linklist_t* p,int start,int kill)//start为开始的位置,需要包括第0个节点。
													//kill为每隔几只猴子 杀一个比如 0 1 2 3 4
													//从0开始 kill=2那就杀3,此时是创建了5个节点
													//createJoseph(p,5)
													//其实逻辑上很好理解,就是不太好确认位置
													//画两次图就好了^^
{
	linklist_t* t=p;
	int i=0;
	for(i=0;i<start;i++)
	{
		t=t->next;
	}
	//move to the front of killone;

	linklist_t* tt;
	while(t!=t->next){
		for(i=0;i<kill;i++)
		{
			t=t->next;
		}
		//kill
		tt=t->next;
		t->next=t->next->next;
		tt->next=NULL;
		free(tt);
		tt=NULL;
	}
	printf("%d is the monky king\n",t->data);
}

本周总结

本周上了3节数据结构的课,虽然在学校中学过,但是像华清这样一天讲一个框架的进度,还是需要继续努力,晚自习多看多写多思考。目前写的内容虽然简单,但是思路的开阔也很关键,开阔思路不会因为它简单,拓展的机会就没有,发挥想象力总结会有收获的,加油。

第二周

本周学习完成了数据结构部分进行了一天考试,周五结束的时候开了新课IO,更新的第二周,我感觉像上周一样流水账式的总结除了做复习的参考外没有别的意义,所以我想,从这周开始的更新不再把每周的所有内容展示,只突出个人认为的重点。

栈的特点是只能在一个端进行数据的插入和删除,先入栈的数据后出栈,后入栈的数据先出栈即FILO&LIFO。实现栈的方式有顺序栈和链式栈两种,两种最大的区别就是栈的内存空间开辟是否连续,栈的长度是否固定。由于栈不仅仅只是节点,我们需要控制它实现FILO,就需要再定义一个描述栈的结构体,拿顺序栈来说,我们需要知道栈的首地址,还需要知道栈针的位置,为了判空判满操作的方便,还需要知道栈的长度。拿顺序栈来说,定义描述顺序栈内容的结构体代码如下

typedef int datatype//真的很重要,后面调用需要改变栈中存储的数据类型的时候是真的方便^^
typedef struct
{
	datatype *data;//指向栈的首地址
	int length;//开辟的栈的长度
	int top;//栈针
}stack_seq;

栈针是永远保存当前最新入队的元素的下标,空栈时栈针为-1,每次入栈成功,栈针都要自增。

stack_seq *CreateSeqstack(int length)
{
    stack_seq *p=(stack_seq *)malloc(sizeof(stack_seq));
    if(NULL==p)
    {
        printf("Create Error\n");
        return NULL;
    }
    p->data=(datatype *)malloc(sizeof(datatype)*length);
    if(NULL == p->data)
    {
        printf("Stack Create Error\n");
        return NULL;
    }
    p->length=length;
    p->top=-1;
    return p;
}

在创建新栈的时候,需要先创建好保存栈信息的结构体,这样在申请栈的空间的时候直接赋值让成员变量data指向它就好了,省了些许步骤。
链式栈中,做不到单靠下标来去操作栈,所以干脆就不定义描述链式栈的结构体了,按照链表的思想,只需要找到“头”,一步一步地next下去就好了,所以单纯地定义一个节点结构体就好。

typedef int datatype;
typedef struct node_linklist
{
	datatype data;
	struct node_linklist *next;
}stack_link;
//1.创建一个空的无头链式栈 
void CreateLinkstack(stack_link **ptop)
{
	*ptop=NULL;
}

//2.入栈
int inLinkstack(stack_link **ptop,datatype data)
{
	//定义新节点保存入栈的数据
	stack_link *pnew=(stack_link *)malloc(sizeof(lstack_t));
	if(NULL == pnew)
	{
		printf("malloc new node error.\n");
		return -1;
	}
	//初始化新建节点
	pnew->data=data;
	pnew->next=*ptop;

	*ptop=pnew; 
    return 0;
}

//3.出栈 
datatype outLinkstack(lstack_t **ptop)
{
	datatype data;
	lstack_t *pdel=*ptop;
	if(isEmptyLinkStack(*ptop))
	{
		printf("isEmptyLinkStack error.\n");
		return -1;
	}

	data=pdel->data;
	*ptop=pdel->next;

	free(pdel);
	pdel=NULL;
	
	return data;
}

链栈的入栈
在入栈的时候,如果按照图中所示,每次入栈的数据直接链接到前一个栈顶的next,根据栈的特性,栈针永远指向当前最新入栈的元素,看似入栈方便,但是,后续出栈的时候,就访问不到下一个作为栈顶的元素了,所以,在入栈的时候我们需要将新入栈的元素插入到上一个栈顶元素的“左”,再将栈针的值赋为新元素的地址。这也就导致了在入栈和出栈的时候需要二级指针来进行操作。

队列

实现队列也是顺序队列和链式队列,队列的特点是先进先出后进后出FIFO&LILO,只允许在队列的两端分别进行插入和删除操作,插入的一端叫队尾,删除的一端叫队头。实现队列的过程中,为了提高效率,以调整指针(顺序队列中为头尾下标)代替队列元素的移动,使用数组实现循环队列。

#define N 6
typedef  int  datatype;
typedef struct 
{
    int front;//对头
    int rear;//队尾
    datatype  data[N];
}queue_seq;
//1.创建一个空的顺序队列
queue_seq *createQueue(void)
{
	queue_seq *p=(queue_t *)malloc(sizeof(queue_seq));
	if(NULL == p)
	{
		printf("createQueue error.\n");
		return NULL;
	}

	//初始化 空的
	p->front=0;
	p->rear=0;
	
	return p;
}

入队操作中,如果把某一下标固定地作为队列的标志就会导致在队列出队后再入队出现问题。
空顺序队列
入队到顺序队列满。
满顺序队列
顺序队列满后,如果队列删除元素后,可以看到队列是有空的,可以再入队了,但是固定下标确定的满传来了错误信息,因为rear的下标等于定义的N的数值-1的数了,按照数组的思想确实已经满了。所以我们采取rear的下标+1(下一个)% N ==head的下标来确定队列满。同样的在计算队列大小的时候也不能单纯的以rear下标减去head下标,也需要适当的模N操作来确定正确的队列大小
(p->rear-p->front + N) % N)+N是因为可能相减得出负数。

删除两个元素后的顺序队列

概念:树(Tree)是(n>=0)个节点的有限集合T,它满足两个条件 :有且仅有一个特定的称为根(Root)的节点;其余的节点可以分为m(m≥0)个互不相交的有限集合T1、T2、……、Tm,其中每一个集合又是一棵树,并称为其根的子树(Subtree)。
特征: 一对多,每个节点最多有一个前驱,但可以有多个后继(根节点无前驱,叶节点无后继)
关于树的一些基本概念
(1)度数:一个节点的子树的个数
(2)树度数:树中节点的最大度数
(3)叶节点或终端节点: 度数为零的节点
(4)分支节点:度数不为零的节点
(5)内部节点:除根节点以外的分支节点 (去掉根和叶子)
(6)节点层次: 根节点的层次为1,根节点子树的根为第2层,以此类推
(7)树的深度或高度: 树中所有节点层次的最大值

二叉树

二叉树(Binary Tree)是n(n≥0)个节点的有限集合,它或者是空集(n=0),或者是由一个根节点以及两棵互不相交的、分别称为左子树和右子树的二叉树组成。二叉树与普通有序树不同,二叉树严格区分左孩子和右孩子,即使只有一个子节点也要区分左右。二叉树:节点最大的度数2。
二叉树性质
二叉树第k(k>=1)层上的节点最多为2的k-1次幂个节点。
深度为k(k>=1)的二叉树最多有2的k次幂-1个节点。
在任意一棵二叉树中,树叶的数目比度数为2的节点的数目多一。n0=n2+1
总节点数为各类节点之和:n = n0 + n1 + n2
总节点数: n = 度数0节点个数 + 度数1节点个数 + 度数2节点个数
满二叉树和完全二叉树
满二叉树: 深度为k(k>=1)时节点为2^k - 1(2的k次幂-1)
完全二叉树:只有最下面两层有度数小于2的节点,且最下面一层的叶节点集中在最左边的若干位置上。

二叉树的创建

根据上面的特点可以确定二叉树的结构,自身,左孩子,右孩子,每一个孩子又是一个树节点。

typedef char tree_datatype;
typedef struct tree_t
{
    tree_datatype data;
    struct tree_t *lchild;
    struct tree_t *rchild;
}bitree;
bitree *CreateBitree()
{
    char ch;
    bitree *r=NULL;
    scanf("%c",&ch);
    if(ch== '#')//输入#就不创建此节点
        return NULL;
    r=(bitree *)malloc(sizeof(bitree));
    if(NULL == r)
    {
        printf("Malloc failed for r\n");
        return NULL;
    }
    r->data =ch;
    r->lchild=CreateBitree();
    r->rchild=CreateBitree();
    return r;
}
//层序遍历
void levelOder_linklist(bitree *r)
{
    if(NULL == r )
        return;
    //创建一个队列
    link_ll * p = createLinkList();
    //让根节点入队
    inLinklist(p,r);
    while(!isEmptyLink(p))
    {
        r=outLinklist(p);
        printf("%c ",r->data);
        if(r->lchild!=NULL)
            inLinklist(p,r->lchild);
        if(r->rchild!=NULL)
            inLinklist(p,r->rchild);
    }

}

层序遍历中选择用队列暂存遍历出的节点是因为队列的先进先出的特点,使用单向链表当然也可以实现,但是实现的思路也是对单向链表使用队列的思想进行操作。完全没有必要,在调用树的层序遍历时使用的头文件不要忘记将重命名的datatype再该回去qaq。
最后是数据结构的思维导图
在这里插入图片描述

本周总结

学完了数据结构给我最直接的感受我用个比喻来说明吧,学数据结构之前,我说话是一个字一个字的说,字与字之间没有很具体的联系,如儿童一样,妈妈,饼干,吃这样,但是学完之后,我就感觉像是把每次要表达的,封装成了一句话,它有我赋予的具体含义,我就可以说,妈妈我想吃这个饼干,或者根据我的需求,改成,爸爸我想给妈妈吃芒果味的曲奇。后续的课程感觉可能就是到达写作文的程度,然后调用别人封装好的“名言警句”去创造自己的文章。再接再厉!

IO进程

IO进程包括标准IO和文件IO,进程包括进程和线程,在学习这部分内容的时候需要记忆的东西很多,不要嫌麻烦,起码把函数名和功能记住,后面再查询man手册也方便。

标准IO

IO:input/output,针对于文件输入输出。
linux有以下文件类型:
.b(块设备) .c(字符设备) .d(目录) -(普通文件) .l(链接文件) .s(套接字) .p(管道)
概念:在C库中定义的一组专门用于输入输出的函数
特点:

	1.标准IO通过缓冲机制减少系统调用的次数,提高效率
	2.标准IO围绕流进行操作,流用FILE *描述;(FILE 就是一个结构体,用于存放操作文件的相关信息
		它在stdio.h文件中定义;vi -t FILE,ctags,ctrl+]:代码追踪,ctrl+t:回退)
	3.标准IO默认打开了三个流,stdin(标准输入)、stdout(标准输出)、stderr(标准出错)都是FILE*类型

缓冲机制
全缓存:与文件相关
刷新缓存的条件: 程序正常退出时刷新、缓存区满刷新、强制刷新
行缓存:与终端相关
刷新缓存的条件: \n刷新、程序正常退出时刷新、缓存区满刷新、强制刷新
不缓存:没有缓存区(stderr)
那么缓冲区多大呢?可以由以下代码测出

#include <stdio.h>
int main(int argc, const char *argv[])
{
	int i;
	for(i = 0; i < 600; i++)
		printf("%04d", i);
	while(1);
}

while(1)的功能是将程序卡在这,这时就必须ctrl+c结束程序,在上面的程序中,没有\n,没有强制刷新,程序也不是正常的退出,那么这时输出的就是缓冲区满的情况,再以每次输出4个int类型的数据,每个int类型的数据4个字节,每个字节8位,就可以计算出缓冲区的大小了。需要注意的是,在循环中,假设我们不知道缓冲区的大体的大小,结束的条件若给的大了,可能会刷出两个缓冲区的内容甚至更多,可以将printf(“%04d”,i)改为printf(“%08d”,i),只要在终端中输出的数小于我们设置的边界就好,每次压缩一点边界,很快就能测出缓冲区的大小了。测试缓冲区大小也可以直接使用以下关键字,开始需要先使用一下标准IO,只有这样才能打开stdout默认输出流。

	//FILE *stdout;
	printf("hello\n");	//调用标准IO
	printf("%d\n", stdout->_IO_buf_end - stdout->_IO_buf_base);
	return 0;

总结一下标准IO中常用的函数

FILE *fopen(const char *path, const char *mode)//打开文件流
int fclose(FILE* stream)//关闭文件流
//每次一个字符的读写
int  fgetc(FILE * stream)//读取文件流中,当前文件指针指向一个字符,读完文件指针后移一个字符
int fputc(int c, FILE * stream)//向文件流中写一个字符,写完后文指针后移一个字符
//每次一行的读写
char * fgets(char *s,int size,FILE * stream)//从文件流中读取size个字符到s中
int  fputs(const char *s,FILE * stream)//向文件流中写入s
//文件定位
void rewind(FILE *stream)//将stream的文件指针定位到开头位置
int fseek(FILE *stream, long offset, int whence)//将stream的文件指针定位到距离whence偏移offset个字符的位置
long ftell(FILE *stream)//返回文件流指针当前的位置

在fopen中第二个参数表示的是打开文件流的方式

r:只读,当文件不存在时报错,文件流定位到文件开头
r+:可读可写,当文件不存在时报错,文件流定位到文件开头
w:只写,文件不存在创建,存在清空
w+:可读可写,文件不存在创建,存在清空
a:追加(在末尾写),文件不存在创建,存在追加,文件流定位到文件末尾
a+:读和追加,文件不存在创建,存在追加,文件流定位到文件末尾
注:当a的方式打开文件时,写只能在末尾进行追加,定位操作是无法改变写的,但是可以移动到其他位置读

课后练习
1:实现cat命令

#include <stdio.h>
int main(int argc, const char *argv[])
{
	FILE *fp;
	int ch = 0;
	fp = fopen(argv[1], "r");
	if(fp == NULL)
	{
		perror("fopen err");
		return -1;
	}
	while((ch = fgetc(fp)) != -1)
	{
		printf("%c", ch);
	}
	fclose(fp)return 0;
}
2. 题目要求:编程读写一个文件test.txt,每隔1秒向文件中写入一行数据,类似这样:
	1,  2007-7-30 15:16:42  
	2,  2007-7-30 15:16:43
	该程序应该无限循环,直到按Ctrl-C中断程序。
	再次启动程序写文件时可以追加到原文件之后,并且序号能够接续上次的序号,比如: 
	1,  2007-7-30 15:16:42
	2,  2007-7-30 15:16:43
	3,  2007-7-30 15:19:02
	4,  2007-7-30 15:19:03
    5,  2007-7-30 15:19:04

其中使用了两个time.h的函数 time(); //计算秒数和 localtime(); //将秒数转换成年月日时分秒

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<unistd.h>
#include<string.h>
int main()
{
	FILE *fp;
	fp=fopen("./log.c","a+");
	if(NULL == fp)
	{
		perror("Failed: ");
		return -1;
	}
	time_t now;
	struct tm *tm_now;
	int i=1;
	int s;
	time(&now);
	tm_now = localtime(&now);
	while((s=fgetc(fp))!=EOF)
		{
				if(s=='\n')
					i++;
		}
	while(1)
	{
			fprintf(fp,"%d %04d-%02d-%02d-%02d-%02d-%02d\n",i++,tm_now->tm_year+1900,tm_now->tm_mon+1,tm_now->tm_mday,tm_now->tm_hour,tm_now->tm_min,tm_now->tm_sec);
			fflush(fp);
			sleep(1);

	}
	fclose(fp);
	return 0;

}

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值