今天写了个链表,好累。

本文提供了一个简单的链表操作程序,包括初始化、插入、删除等基本功能,并通过菜单引导用户进行交互式操作。
#include<stdio.h>
#include<stdlib.h>
#include<limits.h>

#define TRUE 1
#define FALSE 0
#define EXIT 0

//定义结点
struct nodetype 
{
	int data;
	struct nodetype *next;
};

typedef struct nodetype *node;
//判断链表是否为空
int IsEmpty(node L)
{
	if(L->next==NULL)
		return TRUE;
	return FALSE;
}
//初始化链表
node ListInit(void)
{
	node L;
	L=(node) malloc ( sizeof(struct nodetype) );
	if(L==NULL)
		printf("Out of memory!\n");
	L->data=0;
	L->next=NULL;

	return L;
}
//获取第i个结点的数值
int GetNode(node L,int i)
{
	int count;
	node p;

	if( IsEmpty(L) ){
		printf("List is empty!\n");
		return INT_MAX;
	}
	else if(i<=0 || i>L->data){
		printf("Insert position is ERRO! Plsease retry.\n");
		return INT_MAX;
	}
	else{
		for(count=0,p=L; count<i; count++)
			p=p->next;
		return p->data;
	}
}
//在第i个结点后插入数值x
void NodeInsert(node L,int i,int x)
{
	node p,p0;
	int count;

	if(i<0 || i>L->data)
		printf("Insert position is ERRO! Plsease retry.\n");
	else{
		for(count=0,p=L; count<i; count++)
			p=p->next;

		p0=(node) malloc ( sizeof(struct nodetype) );
		if(p0==NULL)
			printf("Out of memory!\n");
		else{
			p0->data=x;
			p0->next=p->next;
			p->next=p0;
			L->data++;

			if(i!=0)
				printf("%d was been inserted after Node-%d.\n",x,i);
			else	
				printf("%d was been inserted after Head.\n",x);
		}
	}
}
//删除第i个结点
void NodeDelete(node L,int i)
{
	int count,x;
	node p,pre;

	if( IsEmpty(L) )
		printf("List is empty!\n");

	else if(i<=0 || i>L->data)
		printf("Insert position is ERRO! Please retry.\n");

	else{	
		for(count=0,p=L; count<i; count++){
			pre=p;
			p=p->next;
		}
		x=p->data;
		pre->next=p->next;
		free(p);
		L->data--;
		printf("Node-%d=%d has beed deleted.\n",i,x);
	}
}
//遍历链表
void ListTraverse(node L)
{
	node p=L->next;

	if(p==NULL)
		printf("List is empty.\n");
	else{
		printf("List_number=%d: ",L->data);

		while(p!=NULL){	
			printf("%d ",p->data);
			p=p->next;
		}
	}
	printf("\n");
}
//销毁链表
void ListDestroy(node L)
{
	int n=L->data;
	
	if(n==0)
		printf("List is empty.\n");
	while(n!=0){
		NodeDelete(L,n);
		n--;
	}
	L->data=0;
	L->next=NULL;
}
//打印菜单
void MenuPrint(void)
{
	printf("=======================\n");
	printf("1.Initialize List\n");
	printf("2.Insert x after Node-i\n");
	printf("3.Delete Node-i\n");
	printf("4.Get Node-i\n");
	printf("5.If List is empty\n");
	printf("6.Traverse List\n");
	printf("7.Destroy List\n");
	printf("0.Exit!\n");
	printf("=======================\n");
	printf("\n");
}



//主函数
int main(void)
{
	int i,x;
	int input=1;
	node list;
	//输入选项
	while(input!=EXIT){
		MenuPrint();
		printf("Choose >>>: ");
		scanf("%d",&input);
		if(input<0 || input>7){
			printf("\n");
			printf("Your choice is ERRO!\n");
			printf("\n");
		}
		//处理选项
		switch(input)
		{
			case 1:	
				list=ListInit();
				printf("List has been initialized.\n");
				printf("\n");
				break;
			case 2:	
				printf("Input i x: ");
				scanf(" %d %d",&i,&x);
				printf("\n");
				NodeInsert(list,i,x);
				printf("\n");
				break;
			case 3:
				printf("Input i: ");
				scanf(" %d",&i);
				printf("\n",i);
				NodeDelete(list,i);
				printf("\n");
				break;
			case 4:
				printf("Input i: ");
				scanf(" %d",&i);
				printf("\n");
				printf("Node-%d=%d\n",i,GetNode(list,i));
				printf("\n");
				break;
			case 5:
				if(IsEmpty(list))
					printf("Yes.It is.\n");
				else
					printf("No.It is not.\n");
				printf("\n");
				break;
			case 6:
				ListTraverse(list);
				printf("\n");
				break;
			case 7:	
				ListDestroy(list);
				printf("List has been destroyed.\n");
				printf("\n");
				break;
			case 0:
				input=0;
		}
	}

	system("PAUSE");
	return 0;
}


 说明:(1) 第一步务必输入:1, 即:1.Initialize List 分配表头。

             (2) 在使用时请输入数字对应菜单选项,不要使用字母,否则会出错。

<think>题目要求:对一棵树进行多次子树增加操作,最后输出每个节点的值。 思路:使用DFS序将树转化为线性序列,子树操作转化为区间操作,用差分数组实现区间更新。 注意:节点数N和操作数Q最大为200000,因此需要O(N+Q)的算法。 步骤: 1. 构建树(邻接表,使用链式前向星或vector,但题目要求不用vector,所以用链式前向星) 2. 进行DFS遍历,得到每个节点进入的时间戳in和离开的时间戳out(即子树在DFS序中的区间) 3. 使用差分数组,对于每个操作(p, x),在区间[in[p], out[p]]上加上x,即: diff[in[p]] += x; diff[out[p]+1] -= x; (如果out[p]+1不超过总节点数) 4. 最后对差分数组求前缀和,得到DFS序上每个位置的值。 5. 由于每个节点i的最终值就是差分数组前缀和数组的第in[i]项,因此输出每个节点时,输出前缀和数组的in[i]位置的值。 注意:DFS遍历树时,为了避免递归爆栈(节点数20万),我们使用非递归DFS。 但是,题目要求成函数,所以我们将DFS和主函数分开。 代码结构: - 全局变量定义(树相关、DFS序、差分数组等) - 添加边的函数 - 非递归DFS函数(或者递归DFS,但递归深度可能很大,所以非递归) - 主函数:读入、建树、DFS、处理操作、差分求值、输出 由于题目要求不用vector,我们使用链式前向星(数组模拟链表)存储图。 注意:非递归DFS的实现,需要一个栈,我们用一个数组模拟栈。 具体步骤: 1. 初始化head数组为-1,边表计数器为0。 2. 读入边,添加双向边。 3. 进行非递归DFS(使用栈): - 初始化:将根节点1入栈,标记父节点为0(或-1)。 - 记录每个节点的当前处理到的边(用cur数组),避免重复访问。 - 栈不为空时,取出栈顶节点u: 如果u是第一次访问(即in[u]==0),则记录in[u]=当前时间戳,时间戳++。 然后遍历u的邻接边,跳过父节点,将未访问的子节点入栈,并记录父节点,同时更新当前处理到的边(cur[u])。 如果u的所有子节点都已处理,则记录out[u]=当前时间戳(注意:此时时间戳已经到子树最后一个节点的下一个,所以out[u]应该是最后一个子节点的时间戳,即当前时间戳-1?但我们在处理完所有子节点后记录out[u]为当前时间戳的最大值,实际上在非递归中,当我们回溯时,out[u]就是当前时间戳(因为子节点都已经打上时间戳了,且当前时间戳还没使用)?) 非递归DFS中时间戳的分配: 我们可以在第一次访问节点时分配in(即入栈时分配),然后在出栈时分配out(因为此时子树已经全部访问完毕)。 具体: - 当节点u第一次入栈时,我们分配in[u]=++time_stamp。 - 然后我们寻找u的下一个未访问的子节点,将其入栈(并标记已访问)。 - 当u没有未访问的子节点时,我们将u出栈,并设置out[u]=time_stamp(注意:此时time_stamp已经到达了u的最后一个子节点的时间戳之后,所以out[u]就是当前时间戳,即最后一个子节点的时间戳?实际上,我们不需要再增加时间戳,因为out[u]就是当前已经分配的最大时间戳,即u的子树中最后一个节点的时间戳)。 但是,在非递归DFS中,我们通常这样分配时间戳: in[u] = ++time_stamp; // 入栈时分配 ... // 处理子节点 当u出栈时,out[u] = time_stamp; // 注意,此时time_stamp已经包含了u的所有子树节点,所以out[u]就是当前时间戳(即最后一个子节点的时间戳,但注意,在u出栈后,下一个节点的时间戳还没分配,所以当前时间戳就是u的子树最后一个节点的时间戳) 然而,在非递归DFS中,我们并不在出栈时增加时间戳,所以out[u]就是当前已经分配的最大时间戳(即u的最后一个子节点的时间戳)。因此,在非递归DFS中,我们只需要在入栈时分配时间戳,在出栈时记录out[u]为当前时间戳(因为此时子树已经全部访问完毕,当前时间戳就是子树中最后一个节点的时间戳,注意:这里的时间戳是连续的,u的子树节点的时间戳范围是[in[u], out[u]])。 但是,我们如何记录当前时间戳?在非递归DFS中,我们每入栈一个节点(第一次访问)就分配一个时间戳(++time_stamp),然后当节点出栈时,我们设置out[u]=time_stamp(注意,此时time_stamp已经分配到了u的最后一个子节点的时间戳,所以u的子树范围就是[in[u], time_stamp])。因此,在出栈时,我们设置out[u] = time_stamp(当前已经分配的最大时间戳)即可。 然而,这样会有一个问题:u的子树节点的时间戳是从in[u]到out[u]],而out[u]可能大于u的最后一个子节点的in,因为时间戳是递增的,所以u的子树节点的时间戳范围是连续的[in[u], out[u]],其中out[u]就是子树中最后一个节点的时间戳。 因此,非递归DFS步骤: 初始化栈,将根节点入栈,并标记访问(或者用cur数组来控制,不需要visited数组,因为树没有环,只要记录父节点避免回走即可)。 time_stamp = 0; in[1]=++time_stamp; // 根节点入栈时分配时间戳 将根节点入栈,并设置cur[1]=head[1],parent[1]=0。 while 栈非空: 取栈顶节点u。 从cur[u]开始遍历边: 跳过父节点。 如果子节点v没有被访问过(即in[v]==0),则: 分配in[v]=++time_stamp; 将v入栈,并设置cur[u]为下一条边(为了下次从下一条边继续),同时设置cur[v]=head[v],parent[v]=u。 跳出当前循环,去处理v(即break,然后继续while循环)。 如果u的所有邻接边都处理完了(即没有子节点可以入栈),则: 设置out[u]=time_stamp; // 注意,此时u的子树已经全部访问,当前时间戳就是u的子树最后一个节点的时间戳 将u出栈(栈顶弹出)。 注意:这样处理,每个节点都会入栈一次,出栈一次,且每个节点的每条边都会被检查一次,所以复杂度是O(N)。 但是,在代码实现中,我们用一个栈数组和一个栈顶指针,以及一个cur数组记录每个节点当前遍历到哪条边。 具体代码实现: int stack[MAXN], top = 0; // 栈 int cur[MAXN]; // 每个节点当前遍历到的边 int parent[MAXN]; // 父节点 int in[MAXN], out[MAXN]; // DFS序 int time_stamp = 0; // 初始化 top = 0; stack[top++] = 1; // 根节点入栈 parent[1] = 0; cur[1] = head[1]; in[1] = ++time_stamp; // 根节点时间戳 while (top > 0) { int u = stack[top-1]; // 栈顶 // 从当前边开始遍历 bool found = false; for (int i = cur[u]; i != -1; i = edge[i].next) { int v = edge[i].to; if (v == parent[u]) continue; // 如果v还没有访问(即父节点不是u,且in[v]==0),则访问它 if (in[v] == 0) { // 未访问 cur[u] = edge[i].next; // 更新当前边,下次从下一条边开始 // 访问v parent[v] = u; in[v] = ++time_stamp; stack[top++] = v; cur[v] = head[v]; // 初始化v的当前边 found = true; break; // 跳出for循环,去处理v } } if (!found) { // u的所有子节点都处理完了 out[u] = time_stamp; // 注意:这里就是当前时间戳,因为u的子树已经全部访问 top--; // 弹出u } } 注意:上面的in[v]在访问时设置,而out[u]在出栈时设置。 但是,还有一种法是在入栈时设置in,在出栈时设置out,并且不记录父节点数组(而是通过栈中保存父节点信息),但这里我们记录父节点数组。 另一种非递归DFS(更通用,避免递归爆栈)是使用栈来保存节点和父节点,以及当前边的索引。 我们采用上述方法。 然后,处理Q次操作: 对于每个操作(p, x): 将区间[in[p], out[p]]加上x,用差分数组: diff[in[p]] += x; if (out[p]+1 <= time_stamp) diff[out[p]+1] -= x; // 注意:time_stamp是总时间戳数,也就是N 注意:时间戳范围是1~N。 最后,对差分数组求前缀和,得到数组ans,那么节点i的最终值就是ans[in[i]]。 输出:节点1到N的ans[in[i]],空格分隔。 注意:差分数组的大小要开到MAXN(至少N+5)。 由于操作数Q最大20万,每次操作O(1),前缀和O(N),所以总复杂度O(N+Q)。 注意:使用long long存储节点值,因为操作次数多,累加值可能很大(最大20万*10000=2e9,int可能溢出,所以用long long)。 代码实现: 全局: const int MAXN = 200010; int head[MAXN], cnt_edge; Edge edge[MAXN*2]; // 无向图,边数2*(N-1) int in[MAXN], out[MAXN]; long long diff[MAXN]; // 差分数组 主函数: 读入N,Q 初始化head数组为-1 读入边,添加双向边 非递归DFS(如上) 初始化diff数组为0 处理Q次操作:每次读入p,x,然后更新差分数组 对diff求前缀和(从1到time_stamp,即N) 然后按节点编号1~N输出:输出diff[in[i]] 注意:节点编号从1到N,DFS序时间戳从1到N。 样例验证: 输入1:4 3 边:1-2, 2-3, 2-4 操作:2 10, 1 100, 3 1 假设DFS顺序:1->2->3->4 in[1]=1, out[1]=4 in[2]=2, out[2]=4 in[3]=3, out[3]=3 in[4]=4, out[4]=4 操作: p=2: [2,4] -> diff[2]+=10, diff[5](不存在)所以不用减 p=1: [1,4] -> diff[1]+=100, diff[5]不用 p=3: [3,3] -> diff[3]+=1, diff[4]减1 然后求前缀和: diff[1]=100 diff[2]=100+10=110 diff[3]=110+1=111 diff[4]=111-1=110 (注意:在操作3中,我们在diff[4]减了1,所以diff[4]=111-1=110?不对,因为操作3的更新是:diff[3]+=1, diff[4] -=1,所以: 位置1:100 位置2:110 位置3:111 位置4:111-1=110 然后节点1: in[1]=1 -> 100 节点2: in[2]=2 -> 110 节点3: in[3]=3 -> 111 节点4: in[4]=4 -> 110 输出:100 110 111 110 但是,我们注意到操作2的更新(10)在位置2,操作1的更新(100)在位置1,操作3的更新(1)在位置3,以及位置4减1(操作3的第二个更新)。所以前缀和: diff[1]=100 diff[2]=100+10=110 diff[3]=110+1=111 diff[4]=111-1=110 (这里减1是因为操作3在位置4减了1?不对,操作3的更新是:diff[3] +=1, diff[4] -=1,所以位置3是110+1=111,位置4是111-1=110,然后位置5及以后不用管。 但是,操作2的更新没有在位置5减,所以位置4之后还是110?实际上,差分数组的前缀和到位置4就是最终值。 所以,我们只需要对差分数组做一次前缀和,然后每个节点的值就是diff[in[i]]。 注意:差分数组的大小要开MAXN(至少N+5),因为时间戳最大为N。 代码实现中,我们开diff数组大小为MAXN(200010+5)。 输出:节点1到N,输出diff[in[i]]。 注意:前缀和计算范围是从1到N(时间戳范围)。 具体代码: 求前缀和:for (int i=1; i<=N; i++) diff[i] += diff[i-1]; 然后输出:for (int i=1; i<=N; i++) 输出diff[in[i]] 注意:节点i的DFS序位置是in[i],所以节点的值就是前缀和数组在位置in[i]的值。 但是,在操作过程中,我们可能更新了超过N的位置(比如out[p]+1可能等于N+1),所以开数组时大小要>=N+2。 下面我们代码,注意全局数组的大小。 由于题目要求不用vector,我们使用链式前向星。 注意:边的数量是2*(N-1),所以边数组要开2*MAXN。 代码实现如下:</think>以下是不使用`vector`并采用非递归DFS实现的完整C++解决方案: ```cpp #include <iostream> #include <cstring> using namespace std; const int MAXN = 200010; const int MAXM = 400010; struct Edge { int to; int next; } edge[MAXM]; int head[MAXN], cnt_edge = 0; int in[MAXN], out[MAXN]; long long diff[MAXN]; int stack[MAXN], cur[MAXN], parent[MAXN]; int time_stamp; void add_edge(int u, int v) { edge[cnt_edge].to = v; edge[cnt_edge].next = head[u]; head[u] = cnt_edge++; } void dfs(int root) { int top = 0; stack[top] = root; parent[root] = 0; cur[root] = head[root]; in[root] = ++time_stamp; while (top >= 0) { int u = stack[top]; bool found = false; int i = cur[u]; for (; i != -1; i = edge[i].next) { int v = edge[i].to; if (v == parent[u]) continue; if (in[v] == 0) { cur[u] = edge[i].next; parent[v] = u; in[v] = ++time_stamp; top++; stack[top] = v; cur[v] = head[v]; found = true; break; } } if (!found) { out[u] = time_stamp; top--; } } } int main() { int N, Q; scanf("%d %d", &N, &Q); memset(head, -1, sizeof(head)); for (int i = 1; i < N; i++) { int u, v; scanf("%d %d", &u, &v); add_edge(u, v); add_edge(v, u); } time_stamp = 0; memset(in, 0, sizeof(in)); memset(out, 0, sizeof(out)); dfs(1); memset(diff, 0, sizeof(diff)); for (int i = 0; i < Q; i++) { int p, x; scanf("%d %d", &p, &x); diff[in[p]] += x; if (out[p] + 1 <= N) { diff[out[p] + 1] -= x; } } for (int i = 1; i <= N; i++) { diff[i] += diff[i - 1]; } for (int i = 1; i <= N; i++) { printf("%lld", diff[in[i]]); if (i < N) printf(" "); } printf("\n"); return 0; } ``` ### 代码解释 #### 1. 数据结构 - **链式前向星**:`edge`数组存储边信息,`head`数组存储每个节点的第一条边 - **DFS序**:`in`数组记录节点进入时间,`out`数组记录子树结束时间 - **差分数组**:`diff`数组用于高效处理子树更新操作 - **非递归DFS辅助**:`stack`数组模拟栈,`cur`数组跟踪当前处理边,`parent`数组记录父节点 #### 2. 核心函数 1. **`add_edge(u, v)`**: - 添加双向边到邻接表 - 每条边存储目标节点和指向下一条边的指针 2. **`dfs(root)`**: - 初始化根节点入栈 - 循环处理栈中节点: - 首次访问节点时分配进入时间戳 - 遍历邻接边,跳过父节点 - 发现未访问子节点时入栈继续处理 - 无子节点可处理时记录子树结束时间并出栈 #### 3. 主流程 1. **输入处理**: - 读取节点数`N`和操作数`Q` - 构建树结构(无向图) 2. **DFS遍历**: - 从根节点1开始非递归遍历 - 为每个节点分配`in`和`out`时间戳 3. **处理操作**: - 对每个操作`(p, x)`: - 在`in[p]`处增加`x` - 在`out[p]+1`处减少`x`(若存在) 4. **结果计算**: - 对差分数组求前缀和 - 节点`i`的最终值即为`diff[in[i]]` #### 4. 算法特点 - **时间复杂度**:O(N + Q),高效处理大规模数据 - **空间优化**:避免递归调用栈,使用数组模拟 - **子树更新优化**:通过DFS序将树转为线性序列,差分数组实现O(1)更新 ### 样例验证 **输入1**: ``` 4 3 1 2 2 3 2 4 2 10 1 100 3 1 ``` **输出1**:`100 110 111 110` **输入2**: ``` 6 2 1 2 1 3 2 4 3 6 2 5 1 10 1 10 ``` **输出2**:`20 20 20 20 20 20` ### 相关问题
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值