每一个节点应该保存的数据,有数据指针、指向下一个节点的指针、指针上一个节点的指针。
/* 双向链表节点数据结构 */
typedef struct _DoubleListNode {
void *pData ; //数据指针,用于保存链表元素的个数
struct _DoubleListNode *pNext ; //指向下一个节点的指针
struct _DoubleListNode *pPrev ; //指向前一个节点的指针
} DoubleListNode , *POSITION;
相对于总个链表,肯定需要一个头节点,让头节点的pNext指向第一个节点,而头节点的pPrev指向最后一个节点,而第一个节点的pPrev和最后一个节点的pNext都是指向头节点,这样就形成了循环链表。如果将头节点的的数据域空间用来存储链表节点的个数,这样我们就能明了链表的长度。
1.1 创建链表
/******************************************************************************
** 函数名称: ListCreate
** 函数功能: 创建链表函数
该函数虽然有一个参数,为函数指针,但是在创建函数中并不调用它,
而仅仅是保存它。这种保存而不调用回调函数的方法也叫注册回调函数。
如果不需要销毁元素,则只需要传入NULL即可。
** 入口参数: DestroyFunc:
** 出口参数: 如果创建失败,则返回NULL;成功则返回一个双向链表结构体的指针
******************************************************************************/
LIST *ListCreate(DESTROYFUNC DestroyFunc)
先开辟一个链表结构体空间,同时为头节点的所有成员初始化,初始状态下,pNext和pPrev都是指向本身,头节点的数据域用来保存节点个数。
LIST *pList ;
pList = (LIST *)malloc(sizeof(LIST)) ; //申请存储空间,建立头结点
if (pList) {
/* 初始时,头结点两个指针都指向头结点本身 */
pList->nodeHead.pNext = &(pList->nodeHead) ;
pList->nodeHead.pPrev = &(pList->nodeHead) ;
pList->nodeHead.pData = 0 ; //初始时节点个数为0
pList->DestroyFunc = DestroyFunc ; //注册销毁函数
}
return pList ; //返回链表结构体的指针
1.1.1 获取链表第一个节点位置/******************************************************************************
** 函数名称: ListGetBegin
** 函数功能: 获取链表第一个节点的位置
** 入口参数: pList:要操作的双向链表指针
** 出口参数: pList->nodeHead.pNext:返回头结点指针
******************************************************************************/
POSITION ListGetBegin( PLIST pList )
{
assert(pList) ;
return (pList->nodeHead.pNext) ;
}
1.1.2 获取链表最后一个节点的下一个节点位置/******************************************************************************
** 函数名称: ListGetEnd
** 函数功能: 获取链表最后一个节点的下一个节点的位置
** 入口参数: pList:要操作的双向链表指针
** 出口参数: &(pList->nodeHead):返回最后一个节点的下一个节点地址
******************************************************************************/
POSITION ListGetEnd( PLIST pList )
{
assert(pList) ;
return (&(pList->nodeHead)) ;
}
1.1.3 获取链表的元素个数/******************************************************************************
** 函数名称: ListGetSize
** 函数功能: 获取链表元素的个数
** 入口参数: pList:要操作的双向链表指针
** 出口参数: (unsigned int)(pList->nodeHead.pData):返回链表个数
******************************************************************************/
unsigned int ListGetSize( PLIST pList )
{
assert(pList) ;
return ( (unsigned int)(pList->nodeHead.pData) ) ;
}
1.1.4 由位置获取数据的值/******************************************************************************
** 函数名称: ListGetData
** 函数功能: 由位置获取数据的值
** 入口参数: pList:要操作的双向链表指针
pos :待插入节点的位置指针
** 出口参数:
******************************************************************************/
DATATYPE ListGetData( PLIST pList , POSITION pos )
{
assert(pList) ;
assert(pos) ;
return (pos->pData) ;
}
1.1 插入节点1.1.1 将数据插入到指定位置要想将数据data插入到双向链表中,则必须先为待插入数据data的节点申请存储空间,并且建立指向该节点的指针pNewNode,用于 存储新节点的地址。
/******************************************************************************
** 函数名称: ListInsert
** 函数功能: 将数据插入到链表的指定位置函数
** 入口参数: pList:要操作的双向链表指针
pos :为待插入节点的位置指针
data :为待插入节点的数据指针
** 出口参数: 如果失败,则返回FALSE;如果成功则返回TRUE
******************************************************************************/
BOOL ListInsert( PLIST pList , POSITION pos , DATATYPE data )
插入节点第一步,为待插入数据data的节点申请存储空间;并将data数据存储在新建节点的数据域。
DoubleListNode *pNewNode ;
unsigned int uiTemp = 0 ;
assert(pList) ;
assert(pos) ;
pNewNode = (DoubleListNode *)malloc(sizeof(DoubleListNode)) ;
pNewNode->pData = data ;
插入节点第二步,将待插入节点的前向指针指向pos所指向的节点。
pNewNode->pPrev = pos ;
插入节点第三步,将待插入节点的后向指针指向pos指向的下一个节点。
pNewNode->pNext = pos->pNext ;
插入节点第四步,pos指向节点的下一节点的前向指针指向pNewNode。
pos->pNext->pPrev = pNewNode ;
插入节点第五步,pos指向节点的后向指针pNewNode。
pos->pNext = pNewNode ;
uiTemp = ((unsigned int)(pList->nodeHead.pData)) ;
uiTemp++ ;
(pList->nodeHead.pData) = ( void * ) uiTemp ;
return TRUE ;
成功插入节点后,链表的节点数要加1,返回成功插入的标志。
1.1.1 将数据插入到链表头成为第一个节点也就是说吃插入在头结点后面,成为第一个节点。那么同样要知道头节点的地址,即为&pList->nodeHead。
/******************************************************************************
** 函数名称: ListPushFront
** 函数功能: 将数据插入到链表头函数
** 入口参数: pList:要操作的双向链表指针
data :为待插入节点的数据指针
** 出口参数: 如果失败,则返回FALSE;如果成功则返回TRUE
******************************************************************************/
BOOL ListPushFront( PLIST pList , DATATYPE data )
{
return (ListInsert(pList , &(pList->nodeHead) ,data)) ;
}
1.1.2 将数据插入到链表尾成为最后一个节点这里我们可以调用上面函数实现,只要我们知道链表尾的地址和待插入的数据,我们就能将数据插入到链表尾。链表最后一个节点的地址即为头结点的前向指针pList->nodeHead.pPrev。由于上面返回值只有TRUE和FALSE。则函数类型可以定义为BOOL型。
/******************************************************************************
** 函数名称: ListPushBack
** 函数功能: 将数据插入到链表尾函数
** 入口参数: pList:要操作的双向链表指针
data :为待插入节点的数据指针
** 出口参数: 如果失败,则返回FALSE;如果成功则返回TRUE
******************************************************************************/
BOOL ListPushBack( PLIST pList , DATATYPE data )
{
return (ListInsert(pList , (pList->nodeHead.pPrev) , data)) ;
}
1.2 删除节点1.2.1 删除指定位置的节点
/******************************************************************************
** 函数名称: ListErase
** 函数功能: 删除指定位置节点函数
** 入口参数: pList:要操作的双向链表指针
data :为待插入节点的数据指针
** 出口参数: 如果失败,则返回FALSE;如果成功则返回TRUE
******************************************************************************/
BOOL ListDelete( PLIST pList , POSITION pos )
将指向待删除节点的两个指针重新指向即可,先判断待删除节点是否为头节点。
unsigned int uiTemp = 0 ;
assert(pList) ;
/* 不允许删除头节点 */
assert( pos && (pos != &(pList->nodeHead)) ) ;
让(pos指向节点的前一个节点的后向指针)指向(pos指向节点的下一个节点)。
pos->pPrev->pNext = pos->pNext ;
让(pos指向节点的后一个节点的前向指针)指向(pos指向节点的前一个节点)。
pos->pNext->pPrev = pos->pPrev ;
如果用户定义了销毁函数,则执行调用销毁函数。然后释放待删除节点所占的空间。
if (pList->DestroyFunc) {
pList->DestroyFunc(pos->pData) ;
}
成功删除节点后,链表节点数要减1,并且返回TRUE。
uiTemp = ((unsigned int)(pList->nodeHead.pData)) ;
uiTemp++ ;
(pList->nodeHead.pData) = (void *)uiTemp ;
return TRUE ;
1.1.1 删除链表头节点后的节点(即第一个节点)只要知道第一个节点的地址即可调用上面函数删除第一个节点。而第一个节点的地址为:pList->nodeHead.pNext。
/******************************************************************************
** 函数名称: ListPopFront
** 函数功能: 删除链表头节点函数
** 入口参数: pList:要操作的双向链表指针
** 出口参数: 如果失败,则返回FALSE;如果成功则返回TRUE
******************************************************************************/
BOOL ListPopFront( PLIST pList )
{
return ( ListDelete(pList , (pList->nodeHead.pNext)) ) ;
}
1.1.2 删除链表的尾节点尾节点地址为:pList->nodeHead.pPrev。
/******************************************************************************
** 函数名称: ListPopBack
** 函数功能: 删除链表尾节点函数
** 入口参数: pList:要操作的双向链表指针
** 出口参数: 如果失败,则返回FALSE;如果成功则返回TRUE
******************************************************************************/
BOOL ListPopBack( PLIST pList )
{
return ( ListDelete(pList , (pList->nodeHead.pPrev)) ) ;
}
1.2 按值搜索位置算法从第一个节点开始搜索,直到搜索到数据域为data的节点为止;如果从第一个节点搜索到最后一个节点都没有搜索到数据域为data的节点,则返回NULL。
/******************************************************************************
** 函数名称: ListFind
** 函数功能: 搜索算法函数
** 入口参数: pList:要操作的双向链表指针
data :要查找的匹配数据
CompareFunc:数据匹配比较函数
** 出口参数:
******************************************************************************/
POSITION ListFind( PLIST pList , DATATYPE data , COMPAREFUNC CompareFunc )
{
DoubleListNode *pNode , *pEndNode ;
assert(pList) ;
pNode = ListGetBegin(pList) ; //获取第一个节点位置
pEndNode = ListGetEnd(pList) ; //获取最后一个节点的下一个节点位置
/* 从头到尾开始找 */
while (pNode != pEndNode) {
/* 如果找到则返回 */
if ( 0 == CompareFunc((pNode->pData),data) ) {
return pNode ;
}
pNode = pNode->pNext ;
}
return (NULL) ;
}
1.1 遍历算法这个函数主要是在遍历双向链表的同时要对每一个节点进行怎么样的操作,这个操作由用户自己决定,入口参数只是给定了一个函数指针TRAVERSEFUNC,由用户自己调用。
/******************************************************************************
** 函数名称: ListTraverse
** 函数功能: 遍历算法函数
** 入口参数: pList:要操作的双向链表指针
TraverseFunc:节点数据的遍历操作函数
** 出口参数: 如果失败,则返回 0 ;如果成功则返回 1
******************************************************************************/
void ListTraverse( PLIST pList , TRAVERSEFUNC TraverseFunc )
{
DoubleListNode *pNode , *pEndNode ;
assert(pList) ;
pNode = ListGetBegin(pList) ; //获取第一个节点的位置
pEndNode = ListGetEnd(pList) ; //获取最后一个节点的下一个节点位置
/* 从头到尾开始遍历 */
while (pNode != pEndNode) {
TraverseFunc(pNode->pData) ; //访问数据
}
}
1.2 销毁链表从最后一个节点开始逐个删除节点,一直到头结点为止。最后再释放掉链表所占空间。
/******************************************************************************
** 函数名称: ListDestroy
** 函数功能: 销毁链表函数
** 入口参数: pList:要释放的双链表指针
** 出口参数: void
******************************************************************************/
void ListDestroy( PLIST pList )
{
assert(pList) ;
while( 0 != ListGetSize(pList) ) {
ListPopBack(pList) ; //删除链表尾节点
}
free(pList) ; //释放节点所占的空间
}