[数据结构]不带头单向非循环链表

本文详细介绍了如何使用C语言创建不带头指针的链表,包括动态内存分配、节点结构、链表操作如头插、尾插、增删查改等,并提供了两个测试示例以展示功能应用。

我们有学过,顺序表如何制作,还有一个与其非常相似的结构就是链表的制作,不过链表在数据中的存储不像顺序表一样是按照内存的顺序进行存储的,其在内存中是一块一块的进行存储,具体如何我们可以看看下面这张图

此链表有一个头指针pList,我们要创造的链表为不带头指针的链表,既我们创造一个结构体,其中一部分为数据部分,另外一部分为下一个数据的地址。

如何开辟内存呢?

我们知道如果我们要增加数据,我们无法像静态顺序表一样一开始就定下来最大数据量为多少,因此我们这边采用动态内存的方式进行开辟内存。还有一些具体的功能

如增删查改,具体如何实现我们可以看以下代码

SeqList.h文件

#pragma once
//单向链表,增删查改插
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#define datatype int
typedef struct Single_list
{
	datatype x;
	struct SL* next;
}SL;

//购买一个动态内存空间
SL* buynode();
//尾插
void pushback(SL** head,datatype input);
//打印链表
void printlist(SL* head);
//头插
void pushfront(SL** head, datatype input);
//尾删
void popback(SL** head);
//头删
void popfront(SL** head);
//查找
SL* findlist(SL* head, datatype input);
//改
void modifylist(SL** head, int location, datatype input);
//插入
void insertlist(SL** head, int location, datatype input);

SeqList.c文件

#include "Slist.h"

SL* buynode()
{
	SL*temp = (SL*)malloc(sizeof(SL));
	if (temp == NULL)
	{
		perror("buynode:");
	}
	return temp;
}
void pushback(SL** head,datatype input)
{
	SL* temp = buynode();
	temp->x = input;
	temp->next = NULL;
	if (*head == NULL)
	{
		*head  = temp;
	}
	else
	{
		SL* tail = *head;
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = temp;
	}
}
void printlist(SL* head)
{
	if (head == NULL)
	{
		printf("NULL");
		printf("\n");
		return;
	}
	while (head->next)
	{
		printf("%d --> ", head->x);
		head = head->next;
	}
	printf("%d --> ", head->x);
	printf("NULL");
	printf("\n");
}
void pushfront(SL** head, datatype input)
{
	if (*head == NULL)
	{
		SL* temp = buynode();
		temp->x = input;
		temp->next = NULL;
		*head = temp;
	}
	SL* temp = buynode();
	temp->x = input;
	temp->next = *head;
	*head = temp;
}
void popback(SL** head)
{
	if (*head == NULL)
	{
		printf("目前没有数据填入,请输入数据后再进行删除\n");
		return;
	}
	if ((*head)->next == NULL)
	{
		free(*head);
		*head = NULL;
		return;
	}
	SL* pre = NULL;
	SL* tail = *head;
	while (tail->next)
	{
		pre = tail;
		tail = tail->next;
	}
	pre->next = NULL;
	free(tail);
	tail = NULL;
} 
void popfront(SL** head)
{
	if (*head == NULL)
	{
		printf("目前没有需要删除的数据,请先输入\n");
		return;
	}
	SL* temp;
	temp = (*head)->next;
	free(*head);
	*head = temp;
}
SL* findlist(SL* head, datatype input)
{
	SL* tail = head;
	while (tail->next == NULL)
	{
		if (tail->x == input)
		{
			printf("找到了\n");
			return tail;
		}
		tail = tail->next;
	}
	//判断最后一个数据
	if (tail->x == input)
	{
		printf("找到了\n");
		return tail;
	}
	printf("没找到数据");
}
void modifylist(SL** head, int location, datatype input)
{
	int count = 1;
	SL* tail = *head;
	while (count < location)
	{
		tail = tail->next;
		count++;
	}
	tail->x = input;
}
void insertlist(SL** head, int location, datatype input)
{
	SL* newnode = buynode();
	newnode->x = input;
	if (*head == NULL)
	{
		SL* temp = buynode();
		temp->x = input;
		temp->next = NULL;
		*head = temp;
		return;
	}
	if (location == 1)
	{
		newnode->next = *head;
		return;
	}
	int count = 1;
	SL* pre = NULL;
	SL* tail = *head;
	while (count < location)
	{
		pre = tail;
		tail = tail->next;
		count++;
	}
	pre->next = newnode;
	newnode->next = tail;
}

Test.c文件

#include "Slist.h"

void test1()
{
	SL* Singlelist = NULL;
	pushback(&Singlelist, 1);
	pushback(&Singlelist, 2);
	pushback(&Singlelist, 3);
	pushback(&Singlelist, 4);
	pushfront(&Singlelist, 5);
	pushfront(&Singlelist, 6);
	pushfront(&Singlelist, 7);
	printf("一开始:");
	printlist(Singlelist);
	findlist(Singlelist, 7);
	popback(&Singlelist);
	printf("尾删:");
	printlist(Singlelist);
	printf("头删:");
	popfront(&Singlelist);
	printlist(Singlelist);
	printf("修改后:");
	modifylist(&Singlelist, 3, 3);
	printlist(Singlelist);
	printf("插入:");
	insertlist(&Singlelist, 3, 5);
	printlist(Singlelist);
	findlist(Singlelist, 7);
	return;
}

void test2()
{
	SL* Singlelist = NULL;
	pushback(&Singlelist, 1);
	printlist(Singlelist);
	popfront(&Singlelist);
	printlist(Singlelist);
}

void main()
{
	test1();
}

总之单向非循环链表是一个非常简单的数据结构,读者可以自行阅读代码以及注释理解。

<think>我们正在讨论的是Verilog硬件描述语言,而链表通常是软件编程中的数据结构。在硬件设计中,由于链表的动态内存分配和指针操作与硬件描述的并行性和静态特性符,通常直接实现链表。但是,如果必须用Verilog模拟链表行为,我们可以考虑用寄存器数组和指针(索引)来模拟。由于硬件描述语言中存在动态内存分配,我们将使用固定大小的数组来存储节点,每个节点包含数据和一个指向下一个节点的指针(实际上是数组索引)。我们将实现一个带头节点(即第一个节点就是有效数据节点)的单向链表,且循环(即最后一个节点的指针为特殊值表示空)。注意:这种实现通常用于教学或特定仿真场景,实际硬件设计中链表结构很少见,因为硬件更擅长并行处理固定结构的数据。我们将定义:-一个节点寄存器数组,每个节点包含`data`和`next`字段。-`next`字段是一个整数,表示下一个节点在数组中的索引。我们使用一个特殊值(例如-1)表示空指针(NULL)。由于是带头节点的链表,第一个节点就是链表的头节点。我们将实现以下基本操作(根据需要,可能只实现部分):1.初始化链表2.插入节点(例如在头部插入)3.删除节点由于用户要求是带头节点、单向、无循环链表,我们只实现这些。但是请注意:在硬件中,这种操作通常是并行的,而且需要状态机来控制操作步骤。我们这里给出一个简化的示例,仅展示模型和基本操作。假设我们有一个固定大小的节点数组(例如8个节点),每个节点有两个字段:32位数据和一个整数指针(数组索引,这里用4位表示,因为8个节点只需3位,但为了-1我们使用4位有符号数?实际上我们可能用0~7表示有效节点,而用一个在0~7的值表示NULL,比如8。但为了简单,我们用一个位宽足够的寄存器,用-1表示NULL)。然而,Verilog支持负数的索引,而且我们的数组索引必须是正整数。因此,我们可以用0作为索引的开始,然后用一个大于最大索引的值表示NULL。例如,数组大小是8,索引0~7,那么我们可以用8表示NULL。我们定义:-节点数组:`reg[31:0]data[0:7];`//32位数据,8个节点-指针数组:`reg[3:0]next[0:7];`//每个节点有一个4位的指针,因为8需要4位,且用8表示NULL(因为0~7是索引)-头指针:`reg[3:0]head;`//指向第一个节点,初始为8(NULL)表示空链表-空闲列表指针:`reg[3:0]free_head;`//指向空闲节点的头节点,用于分配节点。初始时,空闲链表包含所有节点(0~7),我们将其连接成一个链表,然后free_head指向0。但是,由于我们是带头节点的链表,所以头指针直接指向第一个节点。我们还需要一个机制来分配和释放节点。因此,我们维护一个空闲链表,使用相同的节点数组,但用另一个头指针(free_head)来管理空闲节点。由于用户只要求带头节点的单向链表,我们这里给出一个简单的框架,并实现一个在链表头部插入节点的操作。步骤:1.初始化:空闲链表将所有节点连接起来,head为8(表示空链表)。2.当需要插入一个新数据时:a.从free_head获取一个空闲节点(如果free_head为8,则链表满,无法插入)b.将该空闲节点从空闲链表移除(更新free_head)c.将新数据放入该节点的data,然后将其next指向当前head指向的节点(即当前链表的第一个节点)d.将head指向这个新节点。删除操作类似,这里先展开。由于硬件描述语言的特殊性,我们通常使用时钟同步的操作,所以我们会用时钟沿来触发操作,并且使用状态机来控制多步操作。但为了简单,我们先展示一个使用时钟的组合逻辑?,插入操作可能组合逻辑完成,因为需要多个步骤。所以我们给出一个简单的状态机和一个时钟沿触发的插入操作。我们只写一个插入操作的例子。注意:以下代码仅为示例,未经过全面测试,可能包含错误。```verilogmodulelinked_list(inputwireclk,inputwirereset,inputwireinsert_en,//插入使能信号inputwire[31:0]data_in,//要插入的数据outputwirefull,//链表满(无空闲节点)outputwireempty//链表空);//定义节点数组reg[31:0]data[0:7];reg[3:0]next[0:7];//指针,0~7表示索引,8表示NULL//链表头指针和空闲链表头指针reg[3:0]head;reg[3:0]free_head;//初始化integeri;always@(posedgeclkorposedgereset)beginif(reset)begin//初始化空闲链表:将0~7连接到一起,free_head指向0free_head<=0;for(i=0;i<7;i=i+1)beginnext[i]<=i+1;//每个节点指向下一个节点endnext[7]<=8;//最后一个空闲节点指向NULL(8)head<=8;//初始链表为空endend//插入操作always@(posedgeclk)beginif(reset)begin//已经在reset中初始化,这里再重复endelseif(insert_en)beginif(free_head!=8)begin//有空闲节点//获取空闲链表的第一个节点reg[3:0]new_node;new_node=free_head;//更新空闲链表头指针:指向下一个空闲节点free_head=next[free_head];//设置新节点的数据和next指针data[new_node]=data_in;next[new_node]=head;//新节点指向原链表的第一个节点//更新链表头指针:指向这个新节点head=new_node;endendendassignfull=(free_head==8);assignempty=(head==8);endmodule```上述代码实现了在链表头部插入一个节点。请注意:-我们没有实现删除操作。-删除操作需要将节点释放回空闲链表,并且要处理链表中的节点指针。此外,由于我们的链表带头节点的,删除头节点相对容易,但删除中间节点需要遍历,这在硬件中可能需要多个周期。这个示例只是最基础的部分,实际应用中可能需要更复杂的状态机来控制。因为用户需要的是“带头单向循环链表”的示例,我们提供了头部插入的示例。如果需要删除或遍历等操作,还需要进一步实现。注意:这样的设计可能适合综合,主要用于仿真或教学理解。另外,在硬件中,遍历链表需要多个周期,而软件中则是一条条的指针操作。由于链表操作在硬件中效率较低,所以实际中应尽量避免。如果必须使用,需要仔细考虑性能。我们提供了插入操作,删除操作留给用户作为练习。请注意,在硬件中实现链表是常见做法,因此请确保有充分的理由使用链表结构。§§相关问题§§1.如何为上述链表添加删除节点的操作?2.Verilog如何实现遍历链表的操作?3.在FPGA上使用链表结构有哪些优缺点?4.除了链表,在硬件设计中还有哪些常见的数据结构?</think> ### Verilog实现带头单向循环链表的解决方案 在Verilog中实现链表结构需要特殊的考虑,因为硬件描述语言(Verilog)主要用于描述硬件电路,而非软件数据结构。然而,可以通过寄存器数组和指针索引模拟链表行为。以下是一个基本实现方案: #### 核心实现思路 1. **节点结构**:使用寄存器数组存储节点数据(`data`)和下一节点索引(`next`) 2. **内存管理**:使用空闲节点列表(`free_head`)管理可用节点 3. **特殊值**:用`NULL_PTR`(如8'hFF)表示空指针 4. **操作实现**:通过状态机控制插入/删除操作时序 #### 示例代码 ```verilog module linked_list ( input clk, input rst, input [7:0] data_in, input insert_en, input delete_en, output reg full, output reg empty ); // 常量定义 localparam NULL_PTR = 8'hFF; localparam MAX_NODES = 8; // 节点存储 reg [7:0] data [0:MAX_NODES-1]; // 数据存储 reg [7:0] next [0:MAX_NODES-1]; // 下一节点指针 reg [7:0] head = NULL_PTR; // 头指针(无头节点) reg [7:0] free_head = 0; // 空闲节点指针 // 初始化空闲链表 integer i; always @(posedge clk or posedge rst) begin if (rst) begin for (i = 0; i < MAX_NODES-1; i = i+1) next[i] = i+1; next[MAX_NODES-1] = NULL_PTR; free_head = 0; head = NULL_PTR; end end // 插入操作(头部插入) always @(posedge clk) begin if (insert_en && free_head != NULL_PTR) begin // 获取空闲节点 reg [7:0] new_node = free_head; free_head = next[free_head]; // 写入数据并连接链表 data[new_node] = data_in; next[new_node] = head; head = new_node; end end // 删除操作(头部删除) always @(posedge clk) begin if (delete_en && head != NULL_PTR) begin reg [7:0] temp = head; head = next[head]; // 释放节点 next[temp] = free_head; free_head = temp; end end // 状态检测 always @(*) begin full = (free_head == NULL_PTR); empty = (head == NULL_PTR); end endmodule ``` #### 关键特性说明 1. **带头节点**:直接使用`head`指向第一个数据节点 2. **单向结构**:每个节点只有`next`指针 3. **无循环**:末节点`next`设为`NULL_PTR` 4. **静态内存**:固定大小数组模拟动态内存 5. **同步操作**:所有操作在时钟上升沿触发 #### 应用注意事项 1. **存储限制**:链表大小由预分配寄存器数量决定 2. **时序约束**:插入/删除操作需多周期完成 3. **无动态分配**:同于软件实现,节点内存是预分配的 4. **访问效率**:遍历操作需要多次时钟周期 在FPGA实现中,该方案可用于特殊场景如: - 数据包缓冲管理 - 动态事件队列 - 有限状态机扩展存储 > 注:硬件中的链表操作相比软件有显著性能差异,应优先考虑专用硬件结构(如FIFO)实现数据存储[^1]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值