[C language] data structure: array, linked list, stack, queue, tree

3 Linear structures

3.1 [Array] Continuous storage

1、什么叫做数组
	元素类型相同,大小相等(数组传参,只要传进去首地址和长度就行)
2、数组的优缺点(相对于链表):
	优点:
		存取速度快
	缺点:
		事先必须知道数组的长度
		插入删除元素很慢
		空间通常是有限制的
		需要大块连续的内存块
		插入删除元素的效率很低

Defining an array requires at least 3 parameters: initial address, length, and effective number.
Define an array yourself with pointers:

# include <stdio.h>
# include <malloc.h>  //包含了malloc函数
# include <stdlib.h>  //包含了exit函数

//定义了一个数据类型,该数据类型的名字叫做struct Arr, 该数据类型含有三个成员,分别是pBase, len, cnt
struct Arr
{
    
    
	int * pBase; //存储的是数组第一个元素的地址
	int len; //数组所能容纳的最大元素的个数
	int cnt; //当前数组有效元素的个数
};

void init_arr(struct Arr * pArr, int length);  //分号不能省
bool append_arr(struct Arr * pArr, int val); //追加
bool insert_arr(struct Arr * pArr, int pos, int val); // pos的值从1开始,表示插入的位置是第几个元素,下标为0的是第一个元素
bool delete_arr(struct Arr * pArr, int pos, int * pVal); //pVal是返回的要删除的值
int get();
bool is_empty(struct Arr * pArr);
bool is_full(struct Arr * pArr);
void sort_arr(struct Arr * pArr);
void show_arr(struct Arr * pArr); 
void inversion_arr(struct Arr * pArr);

int main(void)
{
    
    
	struct Arr arr;
	int val;
	
	init_arr(&arr, 6);
	show_arr(&arr);
	append_arr(&arr, 1);
	append_arr(&arr, 10);
	append_arr(&arr, -3);
	append_arr(&arr, 6);
	append_arr(&arr, 88);
	append_arr(&arr, 11);
	if ( delete_arr(&arr, 4, &val) )
	{
    
    
		printf("删除成功!\n");
		printf("您删除的元素是: %d\n", val);
	}
	else
	{
    
    
		printf("删除失败!\n");
	}
/*	append_arr(&arr, 2);
	append_arr(&arr, 3);
	append_arr(&arr, 4);
	append_arr(&arr, 5);
	insert_arr(&arr, -1, 99);
	append_arr(&arr, 6);
	append_arr(&arr, 7);
	if ( append_arr(&arr, 8) )
	{
		printf("追加成功\n");
	}
	else
	{
		printf("追加失败!\n");
	}
*/	
	show_arr(&arr);
	inversion_arr(&arr);
	printf("倒置之后的数组内容是:\n");
	show_arr(&arr);
	sort_arr(&arr);
	show_arr(&arr);

	//printf("%d\n", arr.len);

	return 0;
}

void init_arr(struct Arr * pArr, int length)
{
    
    
	pArr->pBase = (int *)malloc(sizeof(int) * length);//pArr指针变量所指向的结构体变量中的pBase成员
	if (NULL == pArr->pBase)
	{
    
    
		printf("动态内存分配失败!\n");
		exit(-1); //终止整个程序
	}
	else
	{
    
    
		pArr->len = length;
		pArr->cnt = 0;
	}
	return;
}

bool is_empty(struct Arr * pArr)
{
    
    
	if (0 == pArr->cnt)
		return true;
	else
		return false;		
}

bool is_full(struct Arr * pArr)
{
    
    
	if (pArr->cnt == pArr->len)
		return true;
	else
		return false;
}

void show_arr(struct Arr * pArr)
{
    
    
	if ( is_empty(pArr) )   //pArr就是Arr的地址,&Arr赋给pArr,结果还是Arr的地址
	{
    
    
		printf("数组为空!\n");
	}
	else
	{
    
    
		for (int i=0; i<pArr->cnt; ++i)
			printf("%d  ", pArr->pBase[i]); //int *,pArr->pBase表示pArr指向的主函数中的结构体变量中的pBase成员,
		printf("\n");
	}
}

bool append_arr(struct Arr * pArr, int val)
{
    
    
	//满是返回false
	if ( is_full(pArr) )
		return false;

	//不满时追加
	pArr->pBase[pArr->cnt] = val; 
	(pArr->cnt)++;
	return true;
}

bool insert_arr(struct Arr * pArr, int pos, int val)
{
    
    
	int i;

	if (is_full(pArr))
		return false;

	if (pos<1 || pos>pArr->cnt+1)  //
		return false;

	for (i=pArr->cnt-1; i>=pos-1; --i)
	{
    
    
		pArr->pBase[i+1] = pArr->pBase[i];
	}
	pArr->pBase[pos-1] = val;
	(pArr->cnt)++;
	return true;
}

bool delete_arr(struct Arr * pArr, int pos, int * pVal)
{
    
    
	int i;

	if ( is_empty(pArr) )
		return false;
	if (pos<1 || pos>pArr->cnt)
		return false;

	*pVal = pArr->pBase[pos-1];//把等待删除的元素赋给形参pval指向的主函数的val
	for (i=pos; i<pArr->cnt; ++i)
	{
    
    
		pArr->pBase[i-1] = pArr->pBase[i];
	}
	pArr->cnt--;
	return true;
}

void inversion_arr(struct Arr * pArr)
{
    
    
	int i = 0;
	int j = pArr->cnt-1;
	int t;

	while (i < j)
	{
    
    
		t = pArr->pBase[i];
		pArr->pBase[i] = pArr->pBase[j];
		pArr->pBase[j] = t;
		++i;
		--j;
	}
	return;
}

void sort_arr(struct Arr * pArr)
{
    
    
	int i, j, t;

	for (i=0; i<pArr->cnt; ++i)
	{
    
    
		for (j=i+1; j<pArr->cnt; ++j)
		{
    
    
			if (pArr->pBase[i] > pArr->pBase[j])
			{
    
    
				t = pArr->pBase[i];
				pArr->pBase[i] = pArr->pBase[j];
				pArr->pBase[j] = t;
			}
		}
	}
}	

3.2 [Linked List] Discrete Storage

# include<stdio.h>
typedef int ZHANGSAN; //为int在重新多取一个名字,ZHANGSAN等价于int

typedef struct Student
{
    
    
	int sid;
	char name[100];
	char sex;
}* PSTU, STU; //等价于ST代表了struct Student, PST代表了struct Student *

int main(void)
{
    
    
	int i = 10; //等价于ZHANGSAN i = 10
	ZHANGSAN i = 10; //等价于 int i = 10

	STU st; //struct Student st;
	PSTU ps = &st; //struct Student * ps = &st;
	ps->sid = 99;
	printf("%d\n", ps->sid);

	return 0;
}

Linked list definition:
n nodes are distributed discretely and connected
to each other through pointers.
Each node has only one predecessor node, and each node has only one successor node. The
first node has no predecessor node, and the tail node has no successor node.

专业术语:
		首节点:
				第一个有效节点
		尾节点:
				最后一个有效节点
		头节点:
				头结点的数据类型和首节点的类型一样没有存放有效数据,最最前面的,是在首节点之前的,主要是为了方便对链表的操作。
		头指针:(指向头)
				指向头节点的指针变量
		尾指针:
				指向尾节点的指针

(The head node may be very large and may occupy a large amount of memory. Suppose I want to create a function to
output the values ​​of all linked lists. If you don’t use the head pointer type as a formal parameter, then the
size of the head nodes of different linked lists is different. There is no way to find the formal parameters)

Determining a linked list requires several parameters: (or if a function is expected to operate on the linked list,
we need to receive at least the information of the linked list???
Only one parameter is needed: the head pointer, because through it we can deduce
all the information of the linked list.
( The program of the linked list is best to be typed out by yourself)

Classification:

	单链表
	双链表:
			每一个节点有两个指针域
	
	循环链表
			能通过任何一个节点找到其他所有的节点
	非循环链表

(The garbage memory in java will be released automatically, but C and C++ will not, so it must be released manually, otherwise it will cause memory leaks. delete is equal to free) Algorithm
:

遍历
查找
清空
销毁
求长度
排序
删除节点
r = p->pNext;
p->pNext = p->pNext->pNext;
free(r);

插入节点


Insert the node pointed to by q after the node pointed to by p

方法一:先临时定义一个指向p后面节点的指针r  
r = p->pNext;  //r指向p后面的那个节点  
p->pNext = q;   
q->pNext = r;  

方法二:
q->pNext = p->pNext;  
p->pNext = q;

Define a linked list yourself

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

typedef struct Node
{
    
    
	//定义链表节点
	int data; //数据域
	struct Node * pNext; //指针域,pNext指向了一个和它数据类型一样的下一个节点
}NODE, *PNODE; //NODE等价于struct Node    PNODE等价于struct Node *

//函数声明
PNODE create_list(void);  //创建链表
void traverse_list(PNODE pHead);  //遍历链表
bool is_empty(PNODE pHead);  //判断链表是否为空
int length_list(PNODE);  //求链表长度
bool insert_list(PNODE pHead, int pos, int val);  //在pHead所指向链表的第pos个节点的前面插入一个新的结点,该节点的值是val, 并且pos的值是从1开始
//PNODE pHead指向一个链表,int pos表示插入节点的位置,int val表示插入节点的值
bool delete_list(PNODE pHead, int pos, int * pVal);  //删除链表第pos个节点,并将删除的结点的值存入pVal所指向的变量中,  并且pos的值是从1开始
void sort_list(PNODE);  //对链表进行排序


int main(void)
{
    
    
	PNODE pHead = NULL; //等价于 struct Node * pHead = NULL;
	int val;

	pHead = create_list();  //create_list()功能:创建一个非循环单链表,并将该链表的头结点的地址付给pHead
	traverse_list(pHead);
	
	//insert_list(pHead, -4, 33);
	if ( delete_list(pHead, 4, &val) )
	{
    
    
		printf("删除成功,您删除的元素是: %d\n", val);
	}
	else
	{
    
    
		printf("删除失败!您删除的元素不存在!\n");
	}

	traverse_list(pHead);
	
	//int len = length_list(pHead);
	//printf("链表的长度是%d\n", len);

	//sort_list(pHead);
	//traverse_list(pHead);
	
/*	if ( is_empty(pHead) )
		printf("链表为空!\n");
	else
		printf("链表不空!\n");
*/
	return 0;
}

PNODE create_list(void)
{
    
    
	int len;  //用来存放有效节点的个数
	int i;
	int val; //用来临时存放用户输入的结点的值

	//分配了一个不存放有效数据的头结点
	PNODE pHead = (PNODE)malloc(sizeof(NODE));
	if (NULL == pHead)
	{
    
    
		printf("分配失败, 程序终止!\n");
		exit(-1);
	}
	PNODE pTail = pHead;
	pTail->pNext = NULL;

	printf("请输入您需要生成的链表节点的个数: len = ");
	scanf("%d", &len);
	
	for (i=0; i<len; ++i)
	{
    
    
		printf("请输入第%d个节点的值: ", i+1);
		scanf("%d", &val);
		
		PNODE pNew = (PNODE)malloc(sizeof(NODE));
		if (NULL == pNew)
		{
    
    
			printf("分配失败, 程序终止!\n");
			exit(-1);
		}
		pNew->data = val;
		pTail->pNext = pNew;
		pNew->pNext = NULL;
		pTail = pNew;
	}
	
	return pHead;
}

void traverse_list(PNODE pHead)
{
    
    
	PNODE p = pHead->pNext;

	while (NULL != p)
	{
    
    
		printf("%d  ", p->data);
		p = p->pNext;
	}
	printf("\n");
	
	return;
}

bool is_empty(PNODE pHead)
{
    
    
	if (NULL == pHead->pNext)
		return true;
	else
		return false;
}

int length_list(PNODE pHead)
{
    
    
	PNODE p = pHead->pNext;
	int len = 0;

	while (NULL != p)
	{
    
    
		++len;
		p = p->pNext;
	}

	return len;
}

void sort_list(PNODE pHead)
{
    
    
	int i, j, t;
	int len = length_list(pHead);
	PNODE p, q;
	
	for (i=0,p=pHead->pNext; i<len-1; ++i,p=p->pNext)
	{
    
    
		for (j=i+1,q=p->pNext; j<len; ++j,q=q->pNext)
		{
    
    
			if (p->data > q->data)  //类似于数组中的:  a[i] > a[j]
			{
    
    
				t = p->data;//类似于数组中的:  t = a[i];
				p->data = q->data; //类似于数组中的:  a[i] = a[j];
				q->data = t; //类似于数组中的:  a[j] = t;
			}
		}
	}

	return;
}

//在pHead所指向链表的第pos个节点的前面插入一个新的结点,该节点的值是val, 并且pos的值是从1开始
bool insert_list(PNODE pHead, int pos, int val)
{
    
    
	int i = 0;
	PNODE p = pHead;

	while (NULL!=p && i<pos-1)
	{
    
    
		p = p->pNext;
		++i;
	}

	if (i>pos-1 || NULL==p)
		return false;

	//如果程序能执行到这一行说明p已经指向了第pos-1个结点,但第pos-1个节点是否存在无所谓
	//分配新的结点
	PNODE pNew = (PNODE)malloc(sizeof(NODE));
	if (NULL == pNew)
	{
    
    
		printf("动态分配内存失败!\n");
		exit(-1);
	}
	pNew->data = val;
	
	//将新的结点存入p节点的后面
	PNODE q = p->pNext;
	p->pNext = pNew;
	pNew->pNext = q;

	return true;
}


bool delete_list(PNODE pHead, int pos, int * pVal)
{
    
    
	int i = 0;
	PNODE p = pHead;

	while (NULL!=p->pNext && i<pos-1)
	{
    
    
		p = p->pNext;
		++i;
	}

	if (i>pos-1 || NULL==p->pNext)
		return false;
	
	//如果程序能执行到这一行说明p已经指向了第pos-1个结点,并且第pos个节点是存在的
	PNODE q = p->pNext;  //q指向待删除的结点
	*pVal = q->data;  

	//删除p节点后面的结点
	p->pNext = p->pNext->pNext;
	
	//释放q所指向的节点所占的内存
	free(q);
	q = NULL;
	
	return true;

}

Algorithms: Algorithms in a narrow sense are closely related to the way data is stored

The generalized algorithm has nothing to do with the storage method of data.
Generics: (Gives you a false impression, but the genius has done it from the inside)
The effect achieved by using a certain technology is: different storage methods perform the same operations

The real method of learning algorithms: You can't solve many algorithms at all! ! ! ! ! ! Because many of them belong to mathematics, we find out the answer, as long as we can understand it, but most people can't understand it. We divide it into three steps, follow the process, sentences, and try numbers. This process will definitely make mistakes constantly, so keep making mistakes, keep correcting mistakes, and repeat it many times in order to improve. If you really don’t understand, just memorize it first.

Advantages and disadvantages of linked list:

	优点:
		空间没有限制
		插入删除元素很快
	缺点:
		存取速度很慢。

3.3 [Stack] - one of the two common applications of linear structures (structures for storing data)

definition

一种可以实现“先进后出” 的存储结构
栈类似于箱子

Classification

静态栈 (类似于用数组实现)
动态栈 (类似于用链表实现)

Algorithm (put in, take out)

出栈
压栈(参看Java中线程的例子,成产消费的例子)
 # include <stdio.h>
# include <malloc.h>
# include <stdlib.h>

typedef struct Node//定义节点
{
    
    
	int data;
	struct Node * pNext;
}NODE, * PNODE;

typedef struct Stack//定义栈的参数
{
    
    
	PNODE pTop;//永远指向栈顶元素
	PNODE pBottom;//永远指向栈底元素的下一个没有实际含义的元素
}STACK, * PSTACK;  //PSTACK 等价于 struct STACK *

void init(PSTACK);//初始化
void push(PSTACK, int );
void traverse(PSTACK);
bool pop(PSTACK, int *);
void clear(PSTACK pS);

int main(void)
{
    
    
	STACK S;  //STACK 等价于 struct Stack,只定义两个垃圾值ptop和pbottem,不能叫做栈
	int val;

	init(&S);  //目的是造出一个空栈,pTop和pBottom都指向一个头结点
	push(&S, 1); //压栈
	push(&S, 2);
	push(&S, 3);
	push(&S, 4);
	push(&S, 5);
	push(&S, 6);
	traverse(&S); //遍历输出
	
	clear(&S);
	//traverse(&S); //遍历输出

	if ( pop(&S, &val) )
	{
    
    
		printf("出栈成功,出栈的元素是%d\n", val);
	}
	else
	{
    
    
		printf("出栈失败!\n");
	}

	traverse(&S); //遍历输出

	return 0;
}

void init(PSTACK pS)
{
    
    
	pS->pTop = (PNODE)malloc(sizeof(NODE));//将节点地址放入pTop,此时只有一个节点
	if (NULL == pS->pTop)
	{
    
    
		printf("动态内存分配失败!\n") ;
		exit(-1);
	}
	else
	{
    
    
		pS->pBottom = pS->pTop;//令pTop和pBottom都指向同一个值
		pS->pTop->pNext = NULL; //或者pS->Bottom->pNext = NULL;pS指向的s节点的pTop成员指向的头节点的指针域为空,因为它是最后一个元素的下一个元素
	}
}

void push(PSTACK pS, int val)//添加元素,将val的压进栈中
{
    
    
	PNODE pNew = (PNODE)malloc(sizeof(NODE));//在指针域创建一个新节点
	
	pNew->data = val;//将数据放入新节点数据域
	pNew->pNext = pS->pTop; //pS->Top不能改成pS->Bottom,新的节点的指针域要指向更接近bottom的那个节点,有多个节点时压栈只能放入栈顶,将Top放入新的节点的指针域
	pS->pTop = pNew;//把新的节点地址放入pTop内

	return;
}

void traverse(PSTACK pS)
{
    
    
	PNODE p = pS->pTop;//创造一个节点指向pTop,然后边输出边不断往下移动,直到移动到pBottom

	while (p != pS->pBottom)
	{
    
    
		printf("%d  ", p->data);
		p = p->pNext;
	}
	printf("\n");

	return;
}

bool empty(PSTACK pS)
{
    
    
	if (pS->pTop == pS->pBottom)
		return true;
	else
		return false;
}

//把pS所指向的栈出栈一次,并把出栈的元素存入pVal形参所指向的变量中,如果出栈失败,返回false,否则返回true
bool pop(PSTACK pS, int * pVal)
{
    
    //如果栈为空则出栈失败
	if ( empty(pS) ) //pS本身存放的就是S的地址
	{
    
    
		return false;
	}
	else
	{
    
    
		//要想将pTop下移一位,然后删掉之前的节点不能直接pTop=pTop->pNext,因为删掉节点时,原来的Top节点内存未释放而pTop->pNext还在原来top节点的指针域中,所以需要定义一个r指向原来的top节点
		PNODE r = pS->pTop;
		*pVal = r->data;
		pS->pTop = r->pNext;
		free(r);
		r = NULL;

		return true;
	}
}

//clear清空
void clear(PSTACK pS)
{
    
    
	if (empty(pS))
	{
    
    
		return;
	}
	else
	{
    
    
		PNODE p = pS->pTop;//p指向Top
		PNODE q = NULL;//q先不赋值

		while (p != pS->pBottom)//只要p不是指向最后一个元素,即p指向一个有效元素
		{
    
    
			q = p->pNext;//q指向p下面一个节点
			free(p);//删掉p
			p = q;
		}
		pS->pTop = pS->pBottom;
	}
} 

application

函数调用
中断
表达式求值(用两个栈,一个存放数字,一个存放符号)
内存分配
缓冲处理
迷宫

3.4 [Queues] The second of two common applications of linear structures

定义:
	一种可是实现“先进先出”的存储结构
分类:
	链式队列:用链表实现
		front  头部 删除节点  节点方向是从front指向rear方向	
		rear   尾部 添加节点

	静态队列:用数组实现
		静态对流通常都必须是循环队列,为了减少
		内存浪费。

Explanation of the circular queue:

1、 静态队列为什么必须是循环队列
    静态队列有数组实现,数组中front在下方指向第一个元素,rear在上方指向最后一个元素上面一个元素,当插入元素时,rear上移,当删除元素时,front上移,这样下方的数组永远都用不到了造成浪费,所以采用循环队列,当上移到数组最上面元素时,如果继续上移就跑到数组最  低端,循环使用

2、	循环队列需要几个参数来确定及其含义
	需要2个参数来确定,2个参数不同场合有不同的含义
		
		front  如果front指向第一个元素,那么rear就指向最后一个元素的后一个元素;如果front指向第一个元素之前的一个元素,那么rear就指向最后一个元素。错开是为了方便对链表的操作
			
		rear   
														
		
3、 循环队列各个参数的含义

		2个参数不同场合不同的含义?   
		建议初学者先记住,然后慢慢体会

			1)队列初始化
			front和rear的值都是零
			2)队列非空
			front代表队列的第一个元素
			rear代表了最后一个有效元素的下一个元素
			3)队列空
			front和rear的值相等,但是不一定是零
4、	循环队列入队伪算法讲解
	两步完成:
	1)将值存入r所代表的位置
	2)将r后移,正确写法是 rear = (rear+1)%数组长度
	错误写法:rear=rear+1;
	
5、 循环队列出队伪算法讲解
	front = (front+1) % 数组长度

6、 如何判断循环队列是否为空
	如果front与rear的值相等,
	则队列一定为空

7、 如何判断循环队列是否已满
	预备知识:
		front的值和rear的值没有规律,
		即可以大,小,等。

	两种方式:
		1、多增加一个表标识的参数,用来计算个数,如果个数等于数组长度,则循环队列就满了
		2、少用一个队列中的元素(才一个,不影响的)
		通常使用第二种方法
		如果r和f的值紧挨着,则队列已满
		用C语言伪算法表示就是:
			if( (r+1)%数组长度 == f )
				已满
			else
				不满
# include <stdio.h>
# include <malloc.h>

typedef struct Queue
{
    
    
	int * pBase;
	int front;
	int rear;
}QUEUE;  

//函数声明参数*后可以不写
void init(QUEUE *);
bool en_queue(QUEUE *, int val);  //入队,QUEUE *表示往哪个队里面放值,val表示放入的值
void traverse_queue(QUEUE *);
bool full_queue(QUEUE *);
bool out_queue(QUEUE *, int *);//出队
bool emput_queue(QUEUE *);

int main(void)
{
    
    
	QUEUE Q;
	int val;

	init(&Q);
	en_queue(&Q, 1);
	en_queue(&Q, 2);
	en_queue(&Q, 3);
	en_queue(&Q, 4);
	en_queue(&Q, 5);
	en_queue(&Q, 6);
	en_queue(&Q, 7);
	en_queue(&Q, 8);

	traverse_queue(&Q);

	if ( out_queue(&Q, &val) )
	{
    
    
		printf("出队成功,队列出队的元素是: %d\n", val);
	}
	else
	{
    
    
		printf("出队失败!\n");
	}
	traverse_queue(&Q);

	return 0;
}

void init(QUEUE *pQ)   //pQ存放Q的地址,*pQ = Q
{
    
    
	pQ->pBase = (int *)malloc(sizeof(int) * 6);//pBase代表数组第一个元素的地址,pBase可以近似理解为一个24字节的数组
	pQ->front = 0;
	pQ->rear = 0;
}

bool full_queue(QUEUE * pQ)
{
    
    
	if ( (pQ->rear + 1) % 6 == pQ->front  )
		return true;
	else
		return false;
}

bool en_queue(QUEUE * pQ, int val)
{
    
    
	if ( full_queue(pQ) )
	{
    
    
		return false;
	}
	else
	{
    
    
		pQ->pBase[pQ->rear] = val;
		pQ->rear = (pQ->rear+1) % 6;

		return true;
	}
}

void traverse_queue(QUEUE * pQ)
{
    
    
	int i = pQ->front;

	while (i != pQ->rear)
	{
    
    
		printf("%d  ", pQ->pBase[i]);
		i = (i+1) % 6;
	}
	printf("\n");

	return;
}

bool emput_queue(QUEUE * pQ)
{
    
    
	if ( pQ->front == pQ->rear )
		return true;
	else 
		return false;
}

bool out_queue(QUEUE * pQ, int * pVal)
{
    
    
	if ( emput_queue(pQ) )
	{
    
    
		return false;
	}
	else
	{
    
    
		*pVal = pQ->pBase[pQ->front];
		pQ->front = (pQ->front+1) % 6;

		return true;
	}
}
	队列算法:
						入队
						出队
				队列的具体应用:
						所有和事件有关的操作都有队列的影子。
						(例如操作系统认为先进来的先处理)

3.5 【Recursion】

[The idea of ​​recursion is one of the basic ideas of software thinking. In tree and graph theory, almost all of them are realized by recursion. The simplest, such as finding factorial, which does not have a clear number of executions, is solved by recursion. 】

Definition:
A function calls itself directly or indirectly (a function calling another function is exactly the same as calling itself, all three steps, but it seems a bit weird to people.)

When a function calls another function during its running, the system needs to complete 3 things before running the called function:
1. Pass all actual parameters, return address and other information to the called function and save it
2. Save it for the called function
3. Transfer control to the entry point of the called function .

Before returning to the calling function from the called function, the system should also complete three things:
1. Save the calculation result of the called function;
2. Release the data area of ​​the called function;
3. Transfer the control according to the return address saved by the called function Transfer to the calling function.

When there are multiple functions forming a nested call, according to the principle of "return after call", the information transfer and control transfer between the above functions must be realized through the "stack", that is, the data required by the system to run the entire program The space is arranged in a stack. Whenever a function is called, a storage area is allocated for it on the top of the stack. Whenever a function is exited, its storage area is released. The data area of ​​the currently running function must be at the top of the stack.

There are three conditions for recursion:
1. The recursion must have a clear termination condition
. 2. The size of the data processed by the function must be decreasing
. 3. The conversion must be solvable.

Loop and recursion:
In theory, what can be solved by loop can definitely be transformed into recursion, but this process is a complex mathematical transformation process. Recursion can be solved and may not be transformed into loop. We beginners only need to understand the classic recursive algorithm. As for whether you have the ability to use it, it depends on the individual.

Recursion:
easy to understand
slow speed
large storage space
loop
not easy to understand
fast speed
small storage space

Example:
1. Find factorial

Factorial loop implementation

# include <stdio.h>
int main(void)
{
    
    
	int val;
	int i, mult=1;

	printf("请输入一个数字: ");
	printf("val = ");
	scanf("%d", &val);

	for (i=1; i<=val; ++i)
		mult = mult * i;
	
	printf("%d的阶乘是:%d\n", val, mult);


	return 0;
}

Recursive implementation of factorial

# include <stdio.h>
//假定n的值是1或大于1的值
long f(long n)
{
    
    
	if (1 == n)//倒着写是为了防止等号写成=,倒着写写成=系统必报错
		return 1;
	else
		return f(n-1) * n;
}

int main(void)
{
    
    
	printf("%ld\n", f(100));

	return 0;
}
2.1+2+3+4+。。。+100的和
# include <stdio.h>

long sum(int n)
{
    
    
	if (1 == n)
		return 1;
	else
		return n + sum(n-1);
}

int main(void)
{
    
    
	printf("%ld\n", sum(100));

	return 0;
}
3.汉诺塔
【汉诺塔】这不是线性递归,这是非线性递归!
n=1      1
n=2      3
n=3      7
.........
.........
n=64     2的64次方减1【这是个天文数字,就算世界上最快的计算机
也解决不了,汉诺塔的负责度是2的n次方减1】问题很复杂,但真正解决
问题的编码只有三句。
# include <stdio.h>

void hannuota(int n, char A, char B, char C)
{
    
    
/*
	如果是1个盘子
		直接将A柱子上的盘子从A移到C
	否则
		先将A柱子上的n-1个盘子借助C移到B
		直接将A柱子上的盘子从A移到C
		最后将B柱子上的n-1个盘子借助A移到C
*/
	if (1 == n)
	{
    
    
		printf("将编号为%d的盘子直接从%c柱子移到%c柱子\n", n, A, C);
	}
	else
	{
    
    
		hannuota(n-1, A, C, B);
		printf("将编号为%d的盘子直接从%c柱子移到%c柱子\n", n, A, C);
		hannuota(n-1, B, A, C);
	}
}

int main(void)
{
    
    
	char ch1 = 'A';
	char ch2 = 'B';
	char ch3 = 'C';
	int n;

	printf("请输入要移动盘子的个数: ");
	scanf("%d", &n);

	hannuota(n, 'A', 'B', 'C');


	return 0;
}
4.走迷宫(CS的实现)

Use of recursion:

树和森林就是以递归的方式定义的
树和图的很多算法都是以递归来实现的
很多数学公式就是以递归的方式定义的
	斐波拉契序列
		1 2 3 5 8 13 21 34。。。

Why data structure is difficult to learn: Because computer memory is linear and one-dimensional, and the data we have to process is relatively complex, so how to save so much complex data in the computer is a difficult problem in itself, and the computer is saving It is easier to understand the linear structure, especially arrays and linked lists are just continuous and discrete problems. The linear structure is the focus of our study, because the linear algorithm is relatively mature, and there are related tools such as Arraylist.Linkedlist in both C++ and Java. , but there are no trees and graphs in Java, because the nonlinear structure is too complicated, and its operations are far greater than those of the linear structure. Even SUN didn't make it. List represents a linked list, ArrayList represents a linked list implemented with an array, and Linkedlist is a linked list implemented with a linked list.

A little review: _

Logical structure: (that is, what can be generated in your brain, regardless of storage in the computer)

线性结构(用一根直线穿)
	在计算机内的存储方式:
	数组
	链表
	栈和队列是一种特殊的线性结构,是具体应用。
	(操作受限的线性结构,不受限的应该是在任何地方可以增删改查
	可以用数组和链表实现。只要把链表学会,栈和队列都能搞定,数
	组稍微复杂一些。)				
				非线性:
					树
					图
	物理结构:	
					数组
					链表			

4 Nonlinear structures

(Rely on linked list to achieve)

4.1 【Tree】

Tree definition
Professional definition:
1. There is one and only one node called the root
2. There are several disjoint subtrees, which are themselves a tree

通俗定义:
	1、树是由节点和边组成
	2、每个节点只有一个父节点但可以有多个子节点
	3、但有一个节点例外,该节点没有根节点,此节点称为根节点

专业术语
	节点    父节点      子节点
	子孙    堂兄弟      
	深度:
		从根节点到最底层节点的层数称之为深度
		根节点是第一层
	叶子节点;(叶子就不能劈叉了)
		没有子节点的节点
	非终端节点:
		实际就是非叶子节点。
	根节点既可以是叶子也可以是非叶子节点
	度:
		子节点的个数称为度。(一棵树看最大的)			

Tree classification:
general tree
The number of child nodes of any node is not limited
Binary tree (ordered tree)
The number of child nodes of any node is at most two, and
the position of the child nodes cannot be changed.

分类:
	一般二叉树
	满二叉树
		在不增加树的层数的前提下。无法再多添加一个节点的二叉树就是满二叉树。
	完全二叉树
		如果只是删除了满二叉树最底层最右边的连续若干个节点,这样形成的二叉树就是完全二叉树。
	满二叉树是完全二叉树的一个特例
		
森林
	n个互不相交的树的集合

A general binary tree should be stored in an array, and it must be converted into a complete binary tree first, because if you only store valid nodes (regardless of pre-order, in-order, or post-order), you cannot know what the composition of the tree looks like.

Tree storage (both converted into binary trees for storage)
Binary tree storage
Continuous storage [Complete binary tree]
Advantages:
Finding the parent node and child nodes of a node (including judging whether there are any) is very fast
Disadvantages:
Consuming too much memory space big

	链式存储
	
一般树的存储
	双亲表示法
		求父节点方便
	孩子表示法
		求子节点方便
	双亲孩子表示法
		求父节点和子节点都很方便
	二叉树表示法
		把一个普通树转化成二叉树来存储
具体转换方法:
	设法保证任意一个节点的
		左指针域指向它的第一个孩子
		有指针域指向它的下一个兄弟
	只要能满足此条件,就可以把一个普通树转化成二叉树
	一个普通树转化成的二叉树一定没有右子树

Forest storage
First convert the forest into a binary tree, and then store the binary tree

Binary tree operation
traversal

	先序遍历【先访问根节点】
		先访问根节点
		再先序访问左子树
		再先序访问右子树
	
	中序遍历【中间访问根节点】
		中序遍历左子树
		再访问根节点
		再中序遍历右子树
		
	后序遍历【最后访问根节点】
		先后序遍历左子树
		再后序遍历右子树
		再访问根节点
	
已知两种遍历序列求原始二叉树 
	通过先序和中序 或者 中序和后续我们可以
	还原出原始的二叉树
	但是通过先序和后续是无法还原出原始的二叉树的
	
	换种说法:
		只有通过先序和中序, 或通过中序和后序
		我们才可以唯一的确定一个二叉树				  

Application
The tree is an important form of data organization in the database (for example, the classification of books in the library is divided layer by layer.) The
relationship between the child and parent processes of the operating system itself is a tree.
The inheritance relationship of classes in object-oriented languages ​​​​is itself A tree
Huffman tree (a special case of tree)

Module 3: Finding and Sorting
Half Finding

Sort by:

冒泡排序 
1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。 
2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。 
3. 针对所有的元素重复以上的步骤,除了最后一个。 
4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

插入排序
插入排序是指在待排序的元素中,假设前面n-1(其中n>=2)个数已经是排好顺序的,现将第n个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第n个数的这个序列也是排好顺序的。按照此法对所有元素进行插入,直到整个序列排为有序的过程,称为插入排序

选择排序
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

快速排序  
(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。
(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

归并排序
第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针超出序列尾
将另一序列剩下的所有元素直接复制到合并序列尾	
(先两个两个排,在四个四个排,再八个八个排)

Sorting and Finding Relationships

排序是查找的前提
排序是重点

Knowledge about containers and data structures in Java
Iterator interface
Map
hash table (relative to Java)

Discuss again what is data structure:
data structure research is a knowledge of data structure storage and data operation.
Data storage is divided into two parts:
individual storage and
individual relationship storage
. From a certain perspective, data storage is the core The most important thing is the storage of individual relations, and the storage of individuals can be ignored.

Discuss again what generics are:
the same logical structure, no matter what the physical storage of the logical structure looks like, we can perform the same operations on it (for example, both are linear structures or trees implemented with arrays and linked lists. tree. Take advantage of the overloading technique.)

Guess you like

Origin blog.csdn.net/wilde123/article/details/122026650