单链表的删除简直就是最最基础的知识了,从大二上数据结构到今天我一直都记得如果要删除单链表,那就一定要判断是不是头结点,教科书都是这么教我们的,简单写个例子
while(cur->next->value != key)
cur = cur->next;
if(cur == head)
//头结点
else
//不是头结点
cur->next = cur->next->next;
free();
这段代码已经深深的印在了我的脑子里
删除单链表要使用它的pre指针作为判断,并且要考虑头结点的问题
意思就是我们在做删除的时候要拿到它的pre,但是这是单链表没有pre指针,所以需要我们对代码有些处理,使用
cur->next->value != key
这我相信是大部分人的思路,也是教科书的思路。这样做不好的地方在哪里,在比如你在定义二叉查找树的时候
typedef struct binary_search_tree
{
int value;
struct binary_search_tree *lchild;
struct binary_search_tree *rchild;
}tree_t;
左孩子和右孩子都使用同一个pre,那就更别提B树了。这样带来的麻烦就是:在(以二叉查找树为例)删除操作时,你要多考虑很多种问题,你需要记录应该删除节点的pre指针,还要判断是lchild还是rchild,同时还需要判断是不是头结点。我实现了删除的代码,看看有多长
void delete(tree_t *bs_tree,tree_t **bs_parent,int index)
{
tree_t *p,*t;
if (bs_tree->rchild == NULL)
{
if (index == 0)
{
(*bs_parent)->lchild = bs_tree->lchild;
bs_tree->lchild = NULL;
free(bs_tree);
}
else if (index == 1)
{
(*bs_parent)->rchild = bs_tree->lchild;
bs_tree->lchild = NULL;
free(bs_tree);
}
else
{
*bs_parent = bs_tree->lchild;
free(bs_tree);
}
}
else if (bs_tree->lchild == NULL)
{
if (index == 0)
{
(*bs_parent)->lchild = bs_tree->rchild;
bs_tree->rchild = NULL;
free(bs_tree);
}
else if (index == 1)
{
(*bs_parent)->rchild = bs_tree->rchild;
bs_tree->rchild = NULL;
free(bs_tree);
}
else
{
*bs_parent = bs_tree->rchild;
free(bs_tree);
}
}
else
{
p = bs_tree;
t = bs_tree->lchild;
while(t->rchild != NULL)
{
p = t;
t = t->rchild;
}
if (p == bs_tree)
{
t->rchild = p->rchild;
p->rchild = NULL;
if (index == 0)
{
(*bs_parent)->lchild = t;
}
else if (index == 1)
{
(*bs_parent)->rchild = t;
}
else
{
(*bs_parent) = t;
}
}
else
{
bs_tree->value = t->value;
p->rchild = NULL;
free(t);
}
}
}
void delete_value(tree_t *bs_tree,int key,tree_t **bs_parent,int index)
{
if (bs_tree->value < key)
{
bs_parent = &bs_tree;
delete_value(bs_tree->rchild,key,bs_parent,1);
}
else if (bs_tree->value > key)
{
bs_parent = &bs_tree;
delete_value(bs_tree->lchild,key,bs_parent,0);
}
else
{
if (index == 1 || index == 0)
{
delete(bs_tree,bs_parent,index);
}
else
delete(bs_tree,bs_parent,-1);
}
}
index 0表示左孩子,1表示右孩子,如果-1就是根节点。当然没有注释是因为我不希望你像我这么傻乎乎的做,你只需要知道你要考虑很多可能就好了。
多写一行代码就有一定的概率出错,于是我看到了这篇文章
http://coolshell.cn/articles/8990.html
一开始我是拒绝的。我觉得这不可能完成,这不对,即使二级指针也不能改变pre中的next的地址啊!!
那么我们看一个简单的例子吧
int **p;
int *b = malloc(sizeof(int));
*b = 5;
p = &b;
**p = 3;
printf("%d \n",*b );
应该输出几呢,3!这就是使用二级指针修改的核心原理。
这样,删除链表就这样了
void test_fun(link_t **l,int key)
{
link_t **cur = l;
link_t *entry = *cur;
while(entry->value != key)
{
cur = &entry->next;
entry = *cur;
}
(*cur) = entry->next;
free(entry);
}
那么我上面的删除二叉查找树的代码就可以精简很多了。我不用考虑是不是左、右孩子,也不用考虑是不是根节点!肯定能让代码逻辑性更强的。
今天这份代码基本刷新了我对单链表的认识。。同样的功能可以如此优雅,也可以臃肿不堪:)