链表是一种常用的线性表,可以快捷的进行插入和删除。但在常规的做法中,这些操作需要有动态内存分配的支持。偏偏有些编程环境,想要动态内存分配的话,需要额外添加一些库文件,比较繁琐。如何在没有动态内存分配函数的情况下,实现链表的随机删除和插入特性呢?
其实解决方法很简单,既然系统中不存在malloc和free函数,我们就自己实现一个简易的就行了。由于假设链表中,每个节点都是同类型的元素,所以这里自制的node_malloc和node_free其实远没有真正的复杂,但是又能够满足链表的需要。
我们事先定义一个节点数组,下标可从0~LEN-1。规定,数组元素存在"被占用"和"空闲"状态。
node_malloc函数,就是从0~LEN-1逐个搜索,看哪个元素是"空闲"状态,就标记它为“被占用”,然后返回它的地址。
node_free函数更加简单,把节点的状态赋值为"空闲"即可。
首先,定义一个链表的节点。
typedef struct node
{
int No;
int is_free;
struct node *pre;
struct node *next;
}node;
注意,is_free元素代表这个节点是否是空闲状态。
再定义自己的node_malloc和node_free函数。
typedef node elemtype;
#define NODE_MEM_FREE 0
elemtype* node_malloc(elemtype* mem_start,elemtype* mem_end)
{
elemtype* mem;
for(mem = mem_start;mem<=mem_end;mem++)
{
if(mem->is_free == NODE_MEM_FREE)
{
mem->is_free = !NODE_MEM_FREE;
return mem;
}
}
return 0;
}
void node_free(elemtype* mem_free)
{
mem_free->is_free = NODE_MEM_FREE;
}
这样就能使用内存分配和释放了。
要注意的是,使用node_malloc的使用方法。
1.先定义一个数组,node Array[LEN];
2.node_malloc可从Array中进行内存分配。需要传入开始内存地址和结尾内存地址,如node_malloc(&Array[0],&Array[LEN-1]);
3.node_malloc返回的地址,所代表的空间肯定是一个节点的大小。所以无需指定内存分配长度。
4.不可以释放没有分配过内存的地址,即不要对没有node_malloc的地址进行node_free。
有了这两个函数,就能像平时编写链表一样去调用内存分配和释放了。这样实现的链表,并不能像常规的链表一样,占用的内存可以根据节点数目而变化。但尽管如此,这样的链表依然可以实现随机新增和删除,这才是主要的。
下面实现一些基本的链表操作:新建,遍历,新增,删除,插入。
elemtype* list_Create(elemtype* mem_start,elemtype* mem_end)
{
elemtype* list_head = node_malloc(mem_start,mem_end);
list_head->pre = list_head->next = 0;
return list_head;
}
typedef void(p_node_fun)(elemtype* node);
int list_each(elemtype* list_head,p_node_fun node_fun)
{
elemtype* list = list_head;
int list_len = 0;
while(list!=0)
{
list_len++;
node_fun(list);
list = list->next;
}
return list_len;
}
elemtype* list_add(elemtype* list_head,elemtype* node)
{
elemtype* list = list_head;
if(list == 0)
{
list_head = node;
node->pre = 0;
}
else
{
while(list->next!=0)
{
list = list->next;
}
list->next = node;
node->pre = list;
}
node->next = 0;
return list_head;
}
elemtype* list_delete(elemtype* list_head,elemtype* node)
{
elemtype* pre_node = node->pre;
elemtype* next_node = node->next;
if(pre_node != 0)
{
pre_node->next = next_node;
}
else
{
list_head = next_node;
}
if(next_node != 0)
{
next_node->pre = pre_node;
}
return list_head;
}
void list_insert(elemtype* list_head,elemtype* node_insert_start,elemtype* node)
{
elemtype* next_node = node_insert_start->next;
node->pre = node_insert_start;
node_insert_start->next = node;
node->next = next_node;
if(next_node!=0)
{
next_node->pre = node;
}
}
注意定义了一个函数指针的数据类型p_node_fun。它可以定义遍历链表的时候,对每个节点做的操作。
有了以上的函数后,我们可以新建一个main.c文件,对链表的接口函数进行测试。以下是一些使用链表的示范代码。
#define LEN 10
elemtype List_Mem[LEN];
void fun_node_view(elemtype* node)
{
printf("node->No:%d\n",node->No);
}
void main()
{
int i = 0,list_len = 0;
elemtype* my_list,* node,* temp;
my_list = list_Create(&List_Mem[0],&List_Mem[LEN-1]);
for(i=1;i<LEN/2;i++)
{
node = node_malloc(&List_Mem[0],&List_Mem[LEN-1]);
node->No = i;
list_add(my_list,node);
}
printf("create list and add 4 nodes\n");
list_len = list_each(my_list,fun_node_view);
printf("list_len = %d \n",list_len);
printf("delete 1 node: <2> \n");
temp = my_list->next->next;
list_delete(my_list,temp);
node_free(temp);
list_len = list_each(my_list,fun_node_view);
printf("list_len = %d \n",list_len);
printf("add 1 node : <5> \n");
node = node_malloc(&List_Mem[0],&List_Mem[LEN-1]);
node->No = 5;
list_add(my_list,node);
list_len = list_each(my_list,fun_node_view);
printf("list_len = %d \n",list_len);
printf("insert 1 node : behind <3> ,insert <6> \n");
node = node_malloc(&List_Mem[0],&List_Mem[LEN-1]);
node->No = 6;
temp = my_list->next->next;
list_insert(my_list,temp,node);
list_len = list_each(my_list,fun_node_view);
printf("list_len = %d \n",list_len);
}
输出结果截图如下。
另外,使用链表要注意以下几点:
1.时刻保证链表的长度不大于内存数组长度LEN;
2.经过list_delete之后的节点,要进行node_free,避免浪费内存。
3.对某个节点进行list_delete时,比如list_delete(head,head->next)。之后,千万不要执行node_free(head->next)。因为这个时候,head->next已经改变了,这样释放的根本不是被node_delete删除的节点。正确的做法是先用一个中间变量保存待删除节点的地址,list_delete和node_free都只传入这个中间变量。详情见示范代码的标红处。
4.list_delete返回值是头节点地址,这是为了防止删除的节点刚好是头节点的情况。此时头节点将后移。list_add同理。