数据结构学习笔记(4.线性表之双向链表)
本节知识点:
1.为什么选择双向链表:因为单向链表只能一直指向下一个链表元素,不能获得前一个元素,如果要进行逆序访问操作是极其耗时的,所以引入双向链表。
2.双向链表的结构:在单向链表的基础上增加了一个链表结构pre,如图。
注意:链表第一个元素的前驱pre不是指向头结点head,而是指向NULL,链表尾结点的后继next指向NULL
3.如何将一个单向链表改成双向链表:
第一步 (改变链表的结构加入前驱):
struct Str_DLinkList //每一个链表元素的结构都会包含这个结构 因为当给链表元素强制类型 { //转换成(DLinkListNode* )的时候 其实就是要开始对每个元素中的 DLinkListNode进行赋值了 DLinkListNode* next; DLinkListNode* pre; };
第二步 (改写插入函数):
对于一个尾插法,如图:
(1).正常的链表插入操作,代码如下:
for(i=1; ( (i<pos) && (node->next != NULL) ); i++) { node = node->next; } /*此处的node是要插入元素的前一个值 Node是要删除的值*/ Node -> next = node -> next; node -> next = Node;
(2).把刚刚插入的数据的前驱pre跟前一个数据元素相连,代码如下:
Node->pre = node;
对于一个正常插入,如图:
(1).正常的链表插入操作,代码如下:
for(i=1; ( (i<pos) && (node->next != NULL) ); i++) { node = node->next; } /*此处的node是要插入元素的前一个值 Node是要删除的值*/ Node -> next = node -> next; node -> next = Node;
(2).先判断是不是尾插法,如果是尾插法,就像上一个情况一样,就不进行这一步的操作了,代码如下:
if(NULL != Node->next) //判断是否为尾插法 如果不是进入如下操作 如果是尾插法 最后一个链表元素不当作NULL的前驱 { Node->next->pre = Node; }
(3).把刚刚插入的数据的前驱pre跟前一个数据元素相连,代码如下:
Node->pre = node;
对于一个头插法,如图:
(1).正常的链表插入操作,代码如下:
for(i=1; ( (i<pos) && (node->next != NULL) ); i++) { node = node->next; } /*此处的node是要插入元素的前一个值 Node是要删除的值*/ Node -> next = node -> next; node -> next = Node;
(2).把附近的两个链表的前驱pre都赋值为正确的值,代码如下:
if(NULL != Node->next) //判断是否为尾插法 如果不是进入如下操作 如果是尾插法 最后一个链表元素不当作NULL的前驱 { Node->next->pre = Node; } Node->pre = node;
(3).如果是头插法,要记得把插入的元素结点的前驱pre赋值为NULL,代码如下:
if( node == (DLinkListNode* )head) //如果是头插法 就要将第一个链表元素的前驱写成NULL 不然前驱就变成了头节点了 { Node->pre = NULL; }
(4).第一次插入链表元素,要把游标指向插入的链表元素,代码如下:
if( 0==lhead->length ) //在第一次插入元素的时候 把游标指向第一次个元素 { lhead->slider = Node; }
第三步 (改写删除函数):
有三种情况分别是:删除的是第一个结点元素,删除的是最后一个结点元素,删除的是中间结点元素。
(1).删除第一个结点元素:要注意给下一个结点元素的前驱pre赋值为NULL,不是指向head
(2).删除最后一个结点元素:要注意不要给next的前驱再赋值了,因为next已经为NULL了。并且此时要把游标往再往前面移动一个位置。代码如下:
DLinkListNode* Del_DLinkListNode(DLinkList* head, int pos) { DLinkListNode* ret = NULL; int i = 0; list_head* lhead = (list_head* )head; if(( NULL != lhead) && (pos > 0) && (pos <= lhead->length)) { DLinkListNode* node = (DLinkListNode* )head; for(i=1; i<pos; i++)//执行 pos次 得到的是第pos位置的node 这个方法行不通 { //因为要想删除第pos位置的node 应该先找到它上一个链表元素 node = node->next; //所以这里面i=1 比get函数少执行了一次 得到第pos-1位置的node } /*值得注意的是 此处的node是要删除元素的前一个值 ret是要删除的值*/ ret = node->next; node->next = ret->next; if(NULL != ret->next) //判断删除的值是否为最后一个元素 { ret->next->pre = ret->pre; if(node == (DLinkListNode* )head)//判断删除的值是否为第一个元素 { ret->next->pre = NULL; } if(lhead->slider == ret) //判断删除的节点是否为游标的位置 { lhead->slider = ret->next; } } else { if(lhead->slider == ret) //判断删除的节点是否为游标的位置 { lhead->slider = ret->pre; } } lhead->length--; } return (DLinkListNode*)ret; }
4.双向链表的快速排序:
对于双向链表进行快速排序的效率还不错,比用冒泡排序好很多~~~~~~~此时对快速排序还不是很理解~~~~~等到有了一定理解再回来想想吧!!!
想要提示的一点是,快排是依赖递归实现的,对于递归是有递归层次限制的(其实也是栈溢出的问题),所以快排的最坏情况是已经排序好了的情况,所以对一个链表重复进行快排很容易出现栈溢出的问题!!!
本节代码:
DLinkList.c:
/******************************************************************************************************* 文件名:DLinkList.c 头文件:DLinkList.h 时间: 2013/08/17 作者: Hao 功能: 可以复用 带有增 删 改 查 功能的循环链表 难道: 1.typedef struct Str_DLinkList DLinkListNode; //这个结构体是链表的真身 struct Str_DLinkList //每一个链表元素的结构都会包含这个结构 因为当给链表元素强制类型 { //转换成(DLinkListNode* )的时候 其实就是要开始对每个元素中的 DLinkListNode进行赋值了 DLinkListNode* next; }; 这个链表结构在链表元素中起到的作用 是本节的难点 2.切记一个问题 就是已经是链表中元素的 千万不要再往链表中添加了 否则链表一定出现无穷的错误 3.对于pos值的问题 add、get、del三个函数中 的链表都是 从1开始的到length 0是链表头 在add函数中pos为0的时候是和pos为1的情况是一样的 都是头插法 0~~~~~无穷大 在get函数中pos为0的时候是获得链表头 地址 0~~~~~length 在del函数中pos为0的时候是无效的 del失败 1~~~~~length *******************************************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <malloc.h> #include "DLinkList.h" typedef struct str_list_head //这个是链表头 其实也可以当作一个没有前驱的 链表元素 元素的内容是链表长度 { //DLinkListNode* next; DLinkListNode head; //这个参数要特别重视 每一个链表元素结构的第一个参数一定是 DLinkListNode //因为在寻找链表元素后继的时候 其实就是将链表元素强制类型转换成 DLinkListNode* 然后给next进行赋值 其实就是给 DLinkListNode变量赋值 DLinkListNode* slider; int length; //链表长度 }list_head; /******************************************************************************************************* 函数名: Creat_DLinkListHead 函数功能:创建一个链表的链表头 并给链表头分配空间 参数: void 返回值:ret 成功返回链表头地址 失败返回NULL *******************************************************************************************************/ DLinkList* Creat_DLinkListHead(void) { list_head* ret = NULL; ret = (list_head* )malloc( sizeof(list_head)*1 ); if(NULL != ret) //malloc分配成功 { ret->length = 0; //ret -> next = NULL; ret->head.next = NULL; ret->head.pre = NULL; ret->slider = NULL; } return (DLinkList* )ret; } /******************************************************************************************************* 函数名:Destroy_DLinkListHead 函数功能:释放一个链表头指针 参数:DLinkList* head 链表头指针 返回值: ret 释放成功返回1 释放失败返回0 *******************************************************************************************************/ int Destroy_DLinkListHead(DLinkList* head) { int ret = 0; list_head* lhead = (list_head* )head; if( NULL != lhead ) { free(lhead); ret = 1; } return ret; } /******************************************************************************************************* 函数名:Get_Length 函数功能:获得链表的长度 参数: DLinkList* head 链表头指针 返回值: ret 成功返回链表长度 失败返回0 *******************************************************************************************************/ int Get_Length(DLinkList* head) { int ret = 0; list_head* lhead = (list_head* )head; if( NULL != lhead ) { ret = lhead -> length; } return ret; } /******************************************************************************************************* 函数名:Clean_DLinkListHead 函数功能: 清空链表 参数: DLinkList* head 链表头指针 返回值:ret 成功返回1 失败返回0 *******************************************************************************************************/ int Clean_DLinkListHead(DLinkList* head) { int ret = 0; list_head* lhead = (list_head* )head; if( NULL != lhead ) { lhead->length = 0; //lhead -> next = NULL; lhead->head.next = NULL; lhead->head.pre = NULL; lhead->slider = NULL; ret = 1; } return ret; } /******************************************************************************************************* 函数名:Add_DLinkList 函数功能:往链表里面添加一个链表元素 如果pos的值是0(就是链表头)和1(链表的第一元素 链表元素个数是从1开始算的)都是头插法 pos的值大于链表长度是尾插法 这里面pos值得注意的是 i=1 pos为a的时候 是把链表元素插入第a个元素的位置 当i=0 pos为a的时候 是把链表元素插入 第a个元素位置的后面 切忌:这里面0位置是链表头指针 从1开始是链表元素 参数: DLinkList* head链表头指针 DLinkListNode* Node插入元素的指针(被强制类型转化成DLinkListNode*) int pos 插入位置 pos的有效值范围是 从0到无穷大 返回值: ret 插入成功返回1 插入失败返回0 *******************************************************************************************************/ int Add_DLinkList(DLinkList* head, DLinkListNode* Node, int pos) { int ret = 0; int i = 0; list_head* lhead = (list_head* )head; DLinkListNode* node = (DLinkListNode* )head; ret=( NULL != node) && ( NULL != Node) && (pos >= 0); if(1 == ret) { for(i=1; ( (i<pos) && (node->next != NULL) ); i++) { node = node->next; } /*此处的node是要插入元素的前一个值 Node是要删除的值*/ Node -> next = node -> next; node -> next = Node; if(NULL != Node->next) //判断是否为尾插法 如果不是进入如下操作 如果是尾插法 最后一个链表元素不当作NULL的前驱 { Node->next->pre = Node; } Node->pre = node; if( 0==lhead->length ) //在第一次插入元素的时候 把游标指向第一次个元素 { lhead->slider = Node; } if( node == (DLinkListNode* )head) //如果是头插法 就要将第一个链表元素的前驱写成NULL 不然前驱就变成了头节点了 { Node->pre = NULL; } lhead -> length++; } return ret; } /******************************************************************************************************* 函数名:Get_DLinkListNode 函数功能:获得链表中第pos个元素位置的链表元素 链表是从1开始的 0是链表头 pos为0的时候表示get链表头 参数: DLinkList* head链表头指针 int pos获得链表元素的位置 pos的有效取值范围是 1 到 length 0是链表头 返回值: DLinkListNode*类型 第pos个链表元素的地址 *******************************************************************************************************/ DLinkListNode* Get_DLinkListNode(DLinkList* head, int pos) { int ret = 0; int i = 0; list_head* lhead = (list_head* )head; ret=( NULL != lhead) && (pos >= 0) && (pos <= lhead->length); if(1 == ret) { DLinkListNode* node = (DLinkListNode* )head; for(i=0; i<pos; i++) //执行 pos次 得到的是第pos位置的node { node = node->next; } return (DLinkListNode*)node; } return NULL; } /******************************************************************************************************* 函数名:Del_DLinkListNode 函数功能:删除链表中第pos位置的链表元素 参数: DLinkList* head链表头指针 int pos删除链表元素的位置 pos是删除的链表元素的位置 跟get和add中的 pos是配套的 有效取值范围依然是 1到 length 在这个函数里面由于不能删除链表头 所以pos为0的时候无效 返回值: DLinkListNode* ret这个返回值很重要 因为这个删除仅仅是把链表元素踢出了链表 并没有free开辟的内存 应该通过这个返回的地址free 释放内存 删除成功返回 删除链表元素的地址 删除失败返回 NULL *******************************************************************************************************/ DLinkListNode* Del_DLinkListNode(DLinkList* head, int pos) { DLinkListNode* ret = NULL; int i = 0; list_head* lhead = (list_head* )head; if(( NULL != lhead) && (pos > 0) && (pos <= lhead->length)) { DLinkListNode* node = (DLinkListNode* )head; for(i=1; i<pos; i++)//执行 pos次 得到的是第pos位置的node 这个方法行不通 { //因为要想删除第pos位置的node 应该先找到它上一个链表元素 node = node->next; //所以这里面i=1 比get函数少执行了一次 得到第pos-1位置的node } /*值得注意的是 此处的node是要删除元素的前一个值 ret是要删除的值*/ ret = node->next; node->next = ret->next; if(NULL != ret->next) //判断删除的值是否为最后一个元素 { ret->next->pre = ret->pre; if(node == (DLinkListNode* )head)//判断删除的值是否为第一个元素 { ret->next->pre = NULL; } if(lhead->slider == ret) //判断删除的节点是否为游标的位置 { lhead->slider = ret->next; } } else { if(lhead->slider == ret) //判断删除的节点是否为游标的位置 { lhead->slider = ret->pre; } } lhead->length--; } return (DLinkListNode*)ret; } /******************************************************************************************************* 函数名: DLinkList_Slider 函数功能:获得当前游标指向的数据 参数: DLinkList* head 返回值:成功返回 DLinkListNode* ret 失败返回NULL *******************************************************************************************************/ DLinkListNode* DLinkList_Slider(DLinkList* head) { DLinkListNode* ret = NULL; list_head* lhead = (list_head* )head; if( (NULL != lhead)&&(NULL != lhead->slider) )//保证slider是有效的 { ret = lhead->slider; } return ret; } /******************************************************************************************************* 函数名: DLinkList_Reset 函数功能:重置游标 让游标指向head头节点后面的第一个元素 参数: DLinkList* head 返回值:成功返回 当前游标的指向DLinkListNode* ret 失败返回NULL *******************************************************************************************************/ DLinkListNode* DLinkList_Reset(DLinkList* head) { DLinkListNode* ret = NULL; list_head* lhead = (list_head* )head; if(NULL != lhead) { lhead->slider = lhead->head.next; ret = lhead->slider; } return ret; } /******************************************************************************************************* 函数名: DLinkList_Next 函数功能:使游标指向下一个元素 参数: DLinkList* head 返回值:成功返回 前游标的指向DLinkListNode* ret 失败返回NULL *******************************************************************************************************/ DLinkListNode* DLinkList_Next(DLinkList* head) { DLinkListNode* ret = NULL; list_head* lhead = (list_head* )head; if((NULL != lhead)&&(NULL != lhead->slider)) //保证游标是有效的 { ret = lhead->slider; lhead->slider = ret->next; } return ret; } /******************************************************************************************************* 函数名: DLinkList_Pre 函数功能:使游标指向上一个元素 参数: DLinkList* head 返回值:成功返回 前游标的指向DLinkListNode* ret 失败返回NULL *******************************************************************************************************/ DLinkListNode* DLinkList_Pre(DLinkList* head) { DLinkListNode* ret = NULL; list_head* lhead = (list_head* )head; if((NULL != lhead)&&(NULL != lhead->slider)) //保证游标是有效的 { ret = lhead->slider; lhead->slider = ret->pre; } return ret; } /******************************************************************************************************* 函数名: DLinkList_Del 函数功能:删除链表中的某个指定元素 参数: DLinkList* head DLinkListNode* node为指定的元素 返回值:成功返回 删除的链表元素 失败返回NULL *******************************************************************************************************/ DLinkListNode* DLinkList_Del(DLinkList* head,DLinkListNode* node) { //这个函数主要是用来删除游标的返回值的 DLinkListNode* ret = NULL; list_head* lhead = (list_head* )head; int i=0; if((NULL != head)&&(NULL != node)) { DLinkListNode* current = (DLinkListNode*)lhead; for(i=1; i<=lhead->length; i++) { if(node == current->next) { ret = current->next; break; } current = current->next; } if(NULL == ret) //说明没有找到node { printf("put error!!!\n"); } else //找到了node { Del_DLinkListNode(lhead,i); printf("ii%d\n",i); } } return ret;//返回删除的链表元素 } /***************************************************************************************************************** 函数名: partion 函数功能:快速排序的子函数 参数: LinkList* pstHead 链表头 LinkListNode* pstLow 开始排序的头指针 LinkListNode* pstHigh 结束排序的尾指针 返回值: LinkListNode* partion 返回中值的指针 注意:way_id是比较的数据 data是交换的数据 *****************************************************************************************************************/ DLinkListNode* partion(DLinkList* pstHead, DLinkListNode* pstLow, DLinkListNode* pstHigh) { list* list_pstLow= (list*) pstLow; list* list_pstHigh= (list*) pstHigh; DATA iTmp; unsigned int pivot = 0; pivot = list_pstLow->data.way_id; while ( pstLow != pstHigh ) { //从后面往前换 while ( (pstLow != pstHigh) && (list_pstHigh->data.way_id >= pivot)) { pstHigh = pstHigh->pre; list_pstHigh = (list*) pstHigh; } //交换high low iTmp = list_pstLow->data; list_pstLow->data = list_pstHigh->data; list_pstHigh->data = iTmp; //从前往后换 while ( pstLow != pstHigh && list_pstLow->data.way_id <= pivot ) { pstLow = pstLow->next; list_pstLow = (list*)pstLow; } //交换high low iTmp = list_pstLow->data; list_pstLow->data = list_pstHigh->data; list_pstHigh->data = iTmp; } return pstLow; } /***************************************************************************************************************** 函数名: quick_sort 函数功能:快速排序 参数: LinkList* pstHead 链表头指针 LinkListNode* pstLow 开始排序的头指针 LinkListNode* pstHigh 结束排序的尾指针 返回值:void 无返回值 *****************************************************************************************************************/ void quick_sort(DLinkList* pstHead, DLinkListNode* pstLow, DLinkListNode* pstHigh) { DLinkListNode* pstTmp = NULL; pstTmp = partion(pstHead, pstLow, pstHigh); if ( pstLow != pstTmp ) { quick_sort(pstHead, pstLow, pstTmp->pre); } if ( pstHigh != pstTmp ) { quick_sort(pstHead, pstTmp->next, pstHigh); } }
DLinkList.h:
#ifndef __DLinkList_H__ #define __DLinkList_H__ typedef void DLinkList; //这个是为了 封装方便 typedef struct Str_DLinkList DLinkListNode; //这个结构体是链表的真身 struct Str_DLinkList //每一个链表元素的结构都会包含这个结构 因为当给链表元素强制类型 { //转换成(DLinkListNode* )的时候 其实就是要开始对每个元素中的 DLinkListNode进行赋值了 DLinkListNode* next; DLinkListNode* pre; }; /**************************************如下参数是为了快速排序罗列的****************************************/ typedef struct _tag_DATA { unsigned int data_length; //dat中前2个字节 表示一条信息的长度 在用多少个字节 unsigned int way_id; //4个字节 表示道路唯一id unsigned int way_name_length;//2个字节 表示道路名称所占字节数 注意:这个不准 unsigned int way_data; //4个字节 表示道路信息 0~3位表示Class番号 4~6位表示岔路数 7位表示有无flag unsigned char Class; //way_data & 0000 0000 0000 0000 0000 0000 0000 1111 0x0f unsigned char byroad_num; //way_data & 0000 0000 0000 0000 0000 0000 0111 0000 0x70 unsigned char flag; //way_data & 0000 0000 0000 0000 0000 0000 1000 0000 0x80 char way_name[256]; //data_length-12个字节 表示道路名称 }DATA; typedef struct _tag_list { DLinkListNode head; DATA data; }list; /****************************************************************************************/ DLinkList* Creat_DLinkListHead(void); int Destroy_DLinkListHead(DLinkList* head); int Get_Length(DLinkList* head); int Clean_DLinkListHead(DLinkList* head); int Add_DLinkList(DLinkList* head, DLinkListNode* Node, int pos); DLinkListNode* Get_DLinkListNode(DLinkList* head, int pos); DLinkListNode* Del_DLinkListNode(DLinkList* head, int pos); DLinkListNode* DLinkList_Slider(DLinkList* head); DLinkListNode* DLinkList_Reset(DLinkList* head); DLinkListNode* DLinkList_Next(DLinkList* head); DLinkListNode* DLinkList_Pre(DLinkList* head); DLinkListNode* DLinkList_Del(DLinkList* head,DLinkListNode* node); #endif
main.c:
#include <stdio.h> #include <stdlib.h> #include "DLinkList.h" typedef struct _tag_str { DLinkListNode head; int i; }str; int main(int argc, char *argv[]) { int j = 0; DLinkList* list_head; list_head = Creat_DLinkListHead(); str str1,str2,str3,str4,str5,str6,*strp; str1.i=1; str2.i=2; str3.i=3; str4.i=4; str5.i=5; str6.i=6; Add_DLinkList(list_head, (DLinkListNode*)&str1,12); Add_DLinkList(list_head, (DLinkListNode*)&str2,12); Add_DLinkList(list_head, (DLinkListNode*)&str3,12); Add_DLinkList(list_head, (DLinkListNode*)&str4,12); Add_DLinkList(list_head, (DLinkListNode*)&str5,12); // Add_DLinkList(list_head, (DLinkListNode*)&str6,0); for(j=1;j<=Get_Length(list_head);j++) { strp = (str* )Get_DLinkListNode(list_head, j); printf("%d\n",strp->i); } printf("\n"); //DLinkList_Reset(list_head); strp = (str* )DLinkList_Slider(list_head); printf("%d\n",strp->i); printf("\n"); for(j=1;j<=Get_Length(list_head)-1;j++) { DLinkList_Next(list_head); strp = (str* )DLinkList_Slider(list_head); printf("%d\n",strp->i); } DLinkList_Del(list_head,(DLinkListNode*)&str5); printf("\n"); for(j=1;j<=Get_Length(list_head)-1;j++) { DLinkList_Pre(list_head); strp = (str* )DLinkList_Slider(list_head); printf("%d\n",strp->i); } Destroy_DLinkListHead(list_head); return 0; }