C双向链表
第一步:建立抽象类型,定义接口
//实现双向链表
#ifndef _LIST_H
#define _LIST_H
#include <stdbool.h>
#define SIZE 20
//抽象一个数据类型
typedef struct _item
{
char title[20];
double value;
}Item;
//定义节点,每个结点包括指向上一个和指向下一个结点的指针
typedef struct _node
{
struct _node * last;
Item item;
struct _node * next;
}Node;
//定义双向链表类型,成员包括头指针、尾部指针
typedef struct _list
{
Node * head;
Node * tail;
}List;
//已定义一个指向链表的指针,将链表初始化
void InitializeList(List * ls);
//判断链表是否已满,(内存是否用尽)
bool ListIsFull(const List * ls);
//判断链表是否为空
bool ListIsEmpty(const List * ls);
//返回链表中项数
unsigned int CountSize(const List * ls);
//已初始化的链表中添加结点
void AddToList(List * ls, Item item);
//非空的链表中,任意位置插入一个结点
void InsertToList(List * ls, int index, Item item);
//已初始化的链表中删除一个结点
void DelInList(List * ls, Item item);
//打印展示非空链表中的数据
void ShowInList(const List * ls);
#endif
第二步:实现接口
自己的一些想法,实现或许不是最优的,还有很多需要改进的地方。以后回过头再来看看。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"
static void CopyToNode(Node * p, Item item)
{
/* 以具体Item类型变化 */
p->item = item;
}
void InitializeList(List * ls)
{
ls->head = ls->tail = NULL;
printf("Initialize successfully!\n");
}
bool ListIsEmpty(const List * ls)
{
bool ret = false;
if(ls->head == NULL && ls->tail == NULL)
ret = true;
printf("The list is %s\n",ret? "empty":"not empty");
return ret;
}
bool ListIsFull(const List * ls)
{
bool ret = false;
Node * p = (Node *) malloc (sizeof(Node));
if(p == NULL)
ret = true;
else
{
ret = false;
free(p);
}
printf("The list is %s\n",ret? "full":"not full");
return ret;
}
unsigned int CountSize(const List * ls)
{
unsigned int count = 0;
Node * p;
for(p = ls->head;p;p = p->next)
count++;
printf("Totally %d node(s)!\n",count);
return count;
}
void AddToList(List * ls, Item item)
{
Node * p = (Node *) malloc (sizeof(Node));
if(p == NULL)
{
fprintf(stderr, "Can't get a new node!\n");
exit(1);
}
CopyToNode(p,item);
p->last = p->next = NULL;
if(ls->head == NULL) //添加第一个双向结点
ls->head = ls->tail = p;
else //添加后续
{
ls->tail->next = p;
p->last = ls->tail;
ls->tail = p;
}
printf("Add to list successfully!\n");
}
void InsertToList(List * ls, int index, Item item)
{
Node * p = (Node *) malloc (sizeof(Node));
if(p == NULL)
{
fprintf(stderr, "Can't get a new node!\n");
exit(2);
}
CopyToNode(p, item);
p->last = p->next = NULL;
if(index < 1 || index > (CountSize(ls) + 1))
{
fprintf(stderr, "Can't insert an item to %d,\
please enter an index from (1 - %d)\n",index, CountSize(ls)+1);
exit(3);
}
Node * q;
int i;
for(i = 1, q = ls->head;q;q = q->next, i++)
{
if(ListIsEmpty(ls))
{
ls->head = ls->tail = p;
break;
}else{
if(index == 1) //当插头部
{
p->next = ls->head;
ls->head->last = p;
ls->head = p;
break;
}else if(index == CountSize(ls) + 1) //当插尾部
{
p->last = ls->tail;
ls->tail->next = p;
ls->tail = p;
break;
}else if(i == index) //当插中间
{
q->last->next = p;
p->last = q->last;
p->next = q;
q->last = p;
break;
}
}
}
printf("Insert successfully!\n");
}
void DelInList(List * ls, Item item)
{
Node * p;
for(p = ls->head;p;p = p->next)
/* 可依据检索条件变化比较方式 */
if(strcmp(p->item.title,item.title) == 0)
{
//删除头结点
if(p == ls->head)
{
ls->head = p->next;
ls->head->last = NULL;
free(p);
break;
//删除尾结点
}else if(p == ls->tail)
{
p->last->next = NULL;
free(p);
break;
//删除中间的结点
}else
{
p->last->next = p->next;
p->next->last = p->last;
free(p);
break;
}
}
printf("Delete successfully!\n");
}
void ShowInList(const List * ls)
{
Node * p;
if(ListIsEmpty(ls))
printf("No data in list!\n");
else{
//正序输出
for(p = ls->head;p;p = p->next)
/* 以具体需要变化 */
printf("title: %s, value: %.1f\n",p->item.title,p->item.value);
printf("\n");
//逆序输出
for(p = ls->tail;p;p = p->last)
/* 以具体需要变化 */
printf("title: %s, value: %.1f\n",p->item.title,p->item.value);
printf("\n");
}
}
至于双向链表有何优势?
- 双向与单向链表唯一的区别就是节点中多一个指向上一个的指针,单向节点中仅有指向下一个,而双向既有指向上一个也有指向下一个的指针。
- 也就意味着我们在查找时双向提供了逆序查找的可能,这种查找方便给我们在编写其他接口如增删改等操作时提供了方便,也许会更方便。
- 但也不一定方便,在实现任意插入的时候可能就会相对麻烦一些,因为插入时得考虑头插、尾插、中间插的区别,头插只需要将新结点的next指向原有的头指针处,last为NULL,将链表头移至新结点即可;尾插,需将新结点链接到链表原尾指针的next,并让新指针last指回上一个,新指针的next为NULL即可,并移动链表尾到新结点;而插在中间的情况,链表的头指针尾指针不需要移动,新指针的last要与上个位置指针双向指,next要与下个位置双向指;
- 总的来说,单向双向各有优势,双向查找起来自然是很方便,比如可以顺序查找或者逆序查找,但实现其他接口时可能就需要多考虑一些,而单向你想逆序查找的话就比较麻烦了;主要还是依据需要选择ADT形式。