数据结构之线性表(顺序表、单链表、双链表)(四)

双向链表


和单向链表相比有以下优势:


插入删除不需要移动元素外,可以原地插入删除


可以双向遍历


结构体定义如下:


typedef struct Node

{

 DateType data;

struct  Node *next,*prior;

}Node,*LinkList;

双向循环链表的操作实现:


在双向循环链表中,有如下指针关系:设指针p指向双向循环链表中的第i个结点,则p->next指向第i+1个结点,p->next->prior仍指向第i个结点,即p->next->prior==p;同样地,p->prior指向第i-1个结点,p->prior->next仍指向第i个结点,即p->prior->next==p。双向循环链表的上述指针关系可以方便算法设计。




双向循环链表指针关系


初始化


int InitList(DLNode **head)

{

 *head = (DLNode *)malloc(sizeof(DlNode));

 (*head)->prior = *head;

 (*head)->next = *head;

 return 1;

}

插入数据元素


和单链表相比,双向循环链表的插入算法指针p可以直接指在第i个结点上,而不需要让指针p指在第i-1个结点上。




双向循环链表的插入过程


删除数据元素


和单链表相比,双向循环链表的删除算法指针p可以直接指在第i个结点上,而不需要让指针p指在第i-1个结点上。




循环双向链表的删除过程


双向链表的C语言实现:

/* run this program using the console pauser or add your own getch, system("pause") or input loop */
// 用到的库文件
#include <stdio.h>  // printf();scanf()
#include <stdlib.h> // exit()
#include <malloc.h> // malloc()
#include <time.h>   // srand((unsigned)time(NULL));
// 函数结果状态代码
#define TRUE    1
#define FALSE   0
#define OK      1
#define ERROR   0
#define INFEASIBLE  -1
#define OVERFLOW    -2
// Status是函数的类型,其值是函数结果状态代码
typedef int Status;
// #define ElemType int  // 也可以用宏定义确定ElemType类型
typedef int ElemType;
// -----双向链表-----
typedef struct  DuLNode {
    ElemType data;                  // 数据域
    struct DuLNode *prior;          // 指向前驱
    struct DuLNode *next;           // 指向后继
} DuLNode, *DuLinkList;
 
// 操作结果:构造一个空的线性表L。
Status InitList_DuL(DuLinkList &L) {
    if(L != NULL)
    {
        printf("线性表已存在!!!");
        return INFEASIBLE;          // 返回-1
    }
    L = (DuLinkList)malloc(sizeof(DuLNode));
    if(!L) {                        // 存储分配失败
        printf("初始化失败!!!");
        exit(OVERFLOW);             // exit(-2)程序异常退出
    }
    L->next = L;                    // 先建立一个带头结点的双向链表,
    L->prior = L;                   // 并使头结点2个指针域指向本身(即头指针L)
    return OK;
}// InitList_DuL
 
// 操作结果:销毁线性表L。
Status DestroyList_DuL(DuLinkList &L) {
    if(!L)
    {
        printf("线性表未初始化。");
        return INFEASIBLE;
    }
    DuLinkList p = L->next, ptmp;   // p指向线性表第一个结点(线性表为空表时,指向头结点)
    while(p != L) {                 // p指向头结点时,循环停止
        ptmp = p->next;
        free(p);                    // 释放每个数据结点的指针域
        p = ptmp;
    }
    free(L);                        // 释放头结点
    L = NULL;
 
    return OK;
}// DestroyList_DuL
 
// 操作结果:将L重置为空表。
Status ClearList_DuL(DuLinkList &L) {
    if(!L)
    {
        printf("线性表未初始化。");
        return INFEASIBLE;
    }
    if(L->next == L)
    {
        printf("线性表本已空!!!");
        return INFEASIBLE;
    }
    DuLinkList p = L->next, ptmp;   // p指向线性表第一个结点
    while(p != L) {                 // p指向头结点时,循环停止
        ptmp = p->next;
        free(p);                    // 释放每个结点的指针域
        p = ptmp;
    }
    L->next = L;                    // 头结点指针域指向本身
    return OK;
}// ClearList_DuL
 
// 操作结果:若L为空表,返回TRUE,否则返回FALSE
Status ListEmpty_DuL(DuLinkList L) {
    if(!L)
    {
        printf("线性表未初始化。");
        return INFEASIBLE;          // 返回-1
    }
    else if(L->next == L)
    {
        printf("线性表为空。");
        return TRUE;                // 返回1
    }
    else
    {
        printf("线性表非空。");
        return FALSE;               // 返回0
    }
}// ListEmpty_DuL
 
// 操作结果:返回L中数据元素个数。
int ListLength_DuL(DuLinkList L) {
    if(!L)
    {
        printf("线性表未初始化。");
        return INFEASIBLE;          // 返回-1
    }
    int nElem = 0;
    DuLinkList p = L->next;         // p指向第一个结点(空表时,指向头结点)
    while(p != L) {
        nElem ++;
        p = p->next;
    }
    return nElem;
}// ListLength
 
// 操作结果:用e返回L中第i个数据元素的值。1≤i≤ListLength(L) 。
Status GetElem_DuL(DuLinkList L, int i, ElemType &e) {
    if(!L) {
        printf("线性表未初始化。");
        return INFEASIBLE;          // 返回-1
    }
    DuLinkList p = L->next;         // p指向第一个结点(空表时,指向头结点)
    int j = 1;                      // j为计数器,统计当p指向第i个数据时,表中已有元素个数
    while ( (p->next != L) && j<i ) // 顺指针向后查找,直到p指向第i个元素或p指向头结点
    {
        p = p->next;
        ++j;
    }
    if ( p==L || j<i || i<1 )       // p == L 指向头结点,说明表为空表。j<i  i的值大于表中现有元素个数。  i<1  传入参数有问题。
    {
        return ERROR;               // 第i个元素不存在
    }
    e = p->data;                    // 取第i个元素
    return OK;
}// GetElem_DuL 算法2.8更改
 
// 操作结果:用p返回L中第i个数据元素的指针。1≤i≤ListLength(L)。
DuLinkList GetElemP_DuL(DuLinkList L, int i) {
    DuLinkList p = L;               // p指向头结点
    int j = 0;                      // j为计数器,统计当p指向第i个结点时,表中已有元素个数
    // p->next == L,表为空。i>j,超出查找范围
    while ( (p->next != L) && j<i ) // 顺指针向后查找,直到p指向第i个元素或p指向头结点
    {
        p = p->next;
        ++j;
    }
    if ( i<1 || i>j+1 )             // i>j+1  i的值大于表长+1。  i<1  传入参数有问题。
    {
        return NULL;                // i大于表长加1时,p=NULL
    }
    // 插入时:i=表长加1,返回头结点;
    if ( i==j+1 )
    {
        return L;
    }
    return p;                       // i<表长加1时,p指向第i个结点;
}// GetElem_DuL 更改
 
// 操作结果:返回L中第1个与e满足compare()(数据元素判定函数)的数据元素的位序,若这样的数据元素不存在,则返回值为0。
Status compare(ElemType listElem, ElemType e) {
    return listElem == e ? TRUE : FALSE;
}// Compare
int LocateElem_DuL(DuLinkList L, ElemType e, Status (*pfn_compare)(ElemType, ElemType)) {
    if(!L) {
        printf("线性表未初始化。");
        return INFEASIBLE;          // 返回-1
    }
    int pos = 1;
    DuLinkList p = L->next;         // p指向第一个结点(空表时,指向头结点)
    while( (p != L) && !(*pfn_compare)(p->data, e) ) {
        ++ pos;
        p = p->next;                // 指针后移p->next = L时,循环回到头结点
    }
    // p == L 指向头结点,说明表为空表。pos>ListLength_DuL(L)  pos的值大于表中现有元素个数。
    if( (p == L) || pos>ListLength_DuL(L) )
    {
        return ERROR;               // 返回0
    }
    return pos;
}// LocateElem_DuL
 
// 操作结果:若cur_e是L的数据元素,则用pre_e返回它的前驱,否则操作失败,pre_e无定义。
Status PriorElem_DuL(DuLinkList L, ElemType cur_e, ElemType &pre_e) {
    int i = LocateElem_DuL(L, cur_e, compare);
    // cur_e为第一个元素,其前驱为最后一个元素
    if(i==0 || i==1)
    {
        GetElem_DuL(L, ListLength_DuL(L), pre_e);
        return OK;
    }
    GetElem_DuL(L, i-1, pre_e);
    return OK;
}// PriorElem_DuL
 
 
// 操作结果:若cur_e是L的数据元素,则用next_e返回它的后继,否则操作失败,pre_e无定义。
Status NextElem_Sq(DuLinkList L, ElemType cur_e, ElemType &next_e) {
    int i = LocateElem_DuL(L, cur_e, compare);
    // cur_e为最后一个元素,其后继为第一个元素
    if(i==0 || i==ListLength_DuL(L))
    {
        GetElem_DuL(L, 1, next_e);
        return OK;
    }
    GetElem_DuL(L, i+1, next_e);
    return OK;
}// NextElem_Sq
 
// 操作结果:在L中第pos个位置插入新的元素e,L的长度加1。1≤pos≤ListLength(L)+1。
Status ListInsert_DuL(DuLinkList &L, int pos, ElemType e) {
    if(!L) {                        // 线性表是否存在
        printf("线性表未初始化。");
        return INFEASIBLE;
    }
    DuLinkList p, s;
    if( !(p = GetElemP_DuL(L, pos) ) ) { // 在L中确定插入位置指针p
        printf("插入位置不合法。");
        return ERROR;               // i等于表长加1时,p指向头结点;i大于表长加1时,p=NULL
    }
    if( !(s = (DuLinkList)malloc(sizeof(DuLNode)) ) ) {// 生成新结点
        printf("存储分配失败。");
        return ERROR;
    }
    s->data = e;                    // 将插入的元素值 赋给 新生成结点的数据域
 
    s->prior = p->prior;            // 插入结点 反向指向 前驱
    p->prior->next = s;             // 前驱 指向 插入结点
    s->next = p;                    // 插入结点 指向 后继
    p->prior = s;                   // 后继 反向指向 插入结点
 
    printf("插入的位置:%d, 插入的元素:%d", pos, e);
    return OK;
}// ListInsert_DuL 算法2.18更改
 
// 操作结果:删除L的第pos个数据元素,并用e返回其值,L的长度减1。1≤pos≤ListLength(L)。
Status ListDelete_DuL(DuLinkList &L, int pos, ElemType &e) {
    if(!L) {                        // 线性表是否存在
        printf("线性表未初始化。");
        return INFEASIBLE;
    }
    DuLinkList p;
    if( !(p = GetElemP_DuL(L, pos)) || p==L) { // 在L中确定第i个元素的位置指针p
        return ERROR;               // p=NULL或p=L时,即第pos个元素不存在
    }
    e = p->data;                    // 要删除结点的数据域,赋给e
    p->prior->next = p->next;       // 删除结点前驱 指向 删除结点后继
    p->next->prior = p->prior;      // 删除结点后继 指向 删除结点前驱
    free(p);                        // 释放指针变量p
    return OK;
}// ListDelete_DuL 算法2.19更改
 
// 操作结果:依次对L的每个数据元素调用函数visit()。一旦vistit()失败,刚操作失败。
Status visit(ElemType e) {
    printf("%d <-> ", e);
    return OK;
}
Status ListTraverse_DuL(DuLinkList L, Status (*pfn_visit)(ElemType)) {
    if(!L) {
        printf("线性表未初始化。");
        return INFEASIBLE;
    }
    if(L->next == L) {
        printf("线性表为空表。");
        return ERROR;
    }
    DuLinkList p = L->next;          // p指向第一个结点
    while(p != L) {
        visit(p->data);
        p = p->next;
    }
    return OK;
}// ListTraverse
 
// 创建随机表,包含10个随机数(头插法)。
void CreateList_DuL_10(DuLinkList &L) {
    // 提供随机数种子
    srand((unsigned)time(NULL));
    // 生成头结点
    L = (DuLinkList)malloc(sizeof(DuLNode));
    if(!L) {
        printf("存储分配失败!!!");
        exit(OVERFLOW);             // exit(-1)程序异常退出
    }
    L->next = L;                    // 头结点指针域指向本身
    L->prior = L;
    for (int i=0; i<10; i++) {      // 生成新结点
        DuLinkList p = (DuLinkList)malloc(sizeof(DuLNode));
        // scanf("%d", &p->data);      // 输入元素值 赋给新生成结点的数据域
        p->data = rand()%100;
        // 插入到表头
        p->next = L->next;          // 插入结点 指向 后继
        p->prior = L;               // 插入结点 反向指向 头结点
        L->prior->next = p;         // 后继 反向指向 插入结点
        L->next = p;                // 头结点 指向 插入结点
 
        //printf("%d ", p->data);     // 查看是否插入了新的元素
    }
}// CreateList_DuL_10
 
// 逆位序输入(随机产生)n个元素的值,建立带表头结点的线性表L(头插法)。
void CreateList_DuL_Head(DuLinkList &L, int n) {
    srand((unsigned)time(NULL));    // 初始化随机数种子
    // 先建立一个带头结点的单链表
    L = (DuLinkList)malloc(sizeof(DuLNode));
    if(!L) {
        printf("存储分配失败!!!");
        exit(OVERFLOW);             // exit(-1)程序异常退出
    }
    L->next = L;                    // 头结点指针域指向本身
    L->prior = L;
    L->data = 5201314;
    for (int i=n; i>0; --i) {
        DuLinkList p = (DuLinkList)malloc(sizeof(DuLNode));// 生成新结点
        // scanf("%d", &p->data);    // 输入元素值
        // 随机生成100以内的数字
        p->data = rand()%100;       // 将生成的元素值赋给新生成结点的数据域
        // p->data = i;             // 将生成的元素值赋给新生成结点的数据域
        // 插入到表头
        p->next = L->next;          // 插入结点 指向 第一个结点
        L->next->prior = p;         // 第一个结点 反向指向 插入结点
        p->prior = L;               // 插入结点 反向指向 头结点
        L->next = p;                // 头结点 指向 插入结点
    }
}// CreateList_DuL 算法2.11更改
 
// 顺位序输入(随机产生)n个元素的值,建立带表头结点的线性表L(尾插法)。
void CreateList_DuL_Tail(DuLinkList &L, int n) {
    srand((unsigned)time(NULL));    // 初始化随机数种子
    // 先建立一个带头结点的单链表
    L = (DuLinkList)malloc(sizeof(DuLNode));
    if(!L) {
        printf("存储分配失败!!!");
        exit(OVERFLOW);             // exit(-1)程序异常退出
    }
    L->next = L;                    // 头结点指针域指向本身
    L->prior = L;
    for (int i=0; i<n; ++i) {
        DuLinkList p = (DuLinkList)malloc(sizeof(DuLNode));
        // scanf("%d", &p->data);    // 输入元素值
        // 随机生成100以内的数字
        p->data = rand()%100;       // 将生成的元素值赋给新生成结点的数据域
        // 插入到表尾
        p->prior = L->prior;        // 插入结点 反向指向 表尾结点
        L->prior->next = p;         // 表尾结点 指向 插入结点
        p->next = L;                // 插入结点 指向 头结点
        L->prior = p;               // 头结点 反向指向 插入结点
    }
}
 
// 初始化菜单
void initMenu() {
    printf("\n\t\t*****************************************\n");
    printf("\n\t\t\t\t  循环双向链表\n");
    printf("\n\t\t  1.创建随机表\t\t  2.构造空线性表\n\t\t  3.销毁线性表\t\t  4.清空线性表\n\t\t  5.线性表是否为空\t  6.线性表的长度");
    printf("\n\t\t  7.查找表中元素\t  8.插入新元素\n\t\t  9.删除某个元素\t 10.遍历线性表\n\t\t 11.回到主菜单\t\t  0.退出");
}
// 回到主菜单
void mainMenu() {
    printf("\n\t\t*****************************************\n");
    printf("\n\t\t\t\t  循环双向链表\n");
    printf("\n\t\t  1.创建随机表\t\t  2.构造空线性表\n\t\t  3.销毁线性表\t\t  4.清空线性表\n\t\t  5.线性表是否为空\t  6.线性表的长度");
    printf("\n\t\t  7.查找表中元素\t  8.插入新元素\n\t\t  9.删除某个元素\t 10.遍历线性表\n\t\t 11.回到主菜单\t\t  0.退出");
}
int main() {
    DuLinkList L = NULL;
    initMenu();
    int select = -1;
    while(select != 0)
    {
        printf("\n\n请选择你的操作:");
        scanf("%d", &select);
        switch(select)
        {
        case 1:// 创建随机表
          {  
          int nCreateOption = -1;
          while(nCreateOption!=0 && nCreateOption!=11)
          {
            printf("\n1.头插法  2.尾插法  3.10个元素的随机表  11.回到主菜单  0.退出查找\n请选择你的操作:");
        scanf("%d", &nCreateOption);
        switch(nCreateOption)
          {
                 case 1:// 1.头插法
         {
           printf("请输入要创建的随机表元素个数:");
           int nElem;
           scanf("%d", &nElem);
           CreateList_DuL_Head(L, nElem);
           printf("头插法创建随机链表:");
               ListTraverse_DuL(L, visit);
             }
            break;
        case 2:// 2.尾插法
        {
          printf("请输入要创建的随机表元素个数:");
          int nElem;
          scanf("%d", &nElem);
          CreateList_DuL_Tail(L, nElem);
              printf("尾插法创建随机链表:");
                  ListTraverse_DuL(L, visit);
            }
        break;
            case 3:// 3.10个元素的随机表
            CreateList_DuL_10(L);
            printf("10个元素的随机表:");
            ListTraverse_DuL(L, visit);
        break;
            case 11:// 11.回到主菜单
            mainMenu();
        break;
        case 0:// 0.退出查找
            break;
        default:
        printf("请输入正确的数字!!!\n");
        break;
            }
        }
        }
            break;
        case 2:// 构造空线性表
            printf("构造一个空的线性表L。");
            InitList_DuL(L);
            break;
        case 3:// 销毁线性表
            printf("销毁线性表L。");
            DestroyList_DuL(L);
            break;
        case 4:// 清空线性表
            printf("将L重置为空表。");
            ClearList_DuL(L);
            break;
        case 5:// 线性表是否为空
            ListEmpty_DuL(L);
            break;
        case 6:// 线性表的长度
            {
                int lLength = ListLength_DuL(L);
                if(lLength != -1) printf("线性表的长度为: %d ", lLength);
            }
            break;
        case 7:// 查找表中元素
            {
                int nSearchOption = -1;
                while(nSearchOption!=0 && nSearchOption!=11)
                {
                    printf("1.按位置查找\t  2.按元素查找\t  11.回到主菜单\t  0.退出查找\n请选择你的操作:");
                    scanf("%d", &nSearchOption);
                    switch(nSearchOption)
                    {
                    case 1:// 1.按位置查找
                        {
                            int pos;
                            ElemType e;
                            printf("请输入要查找的位置:");
                            scanf("%d", &pos);
                            int ret = GetElem_DuL(L, pos, e);
                            if (ret == -1) {
                                printf("\n");
                                break;
                            } else if (ret == 0) {
                                printf("查找位置不正确!\n");
                                break;
                            }
                            printf("第%d个元素的值为:%d ", pos, e);
                            ElemType pre_e, next_e;
                            if(PriorElem_DuL(L, e, pre_e))
                                printf("前一个元素为:%d ", pre_e);
                            if(NextElem_Sq(L, e, next_e))
                                printf("后一个元素为:%d", next_e);
                            printf("\n");
                        }
                        break;// 2.按元素查找
                    case 2:
                        {
                            printf("请输入要查找的元素:");
                            ElemType e;
                            scanf("%d", &e);
                            int pos = LocateElem_DuL(L, e, compare);
                            if (pos == -1) {
                                printf("\n");
                                break;
                            } else if (pos == 0) {
                                printf("没有值为%d的元素。\n", e);
                                break;
                            }
                            printf("值为%d是表中的第%d个元素。\n", e, pos);
                        }
                        break;
                    case 11:// 11.回到主菜单
                        mainMenu();
                        break;
                    case 0:// 0.退出查找
                        break;
                    default:
                        printf("请输入正确的数字!!!\n");
                        break;
                    }
                }
            }
            break;
        case 8:// 插入新元素
            {
                ElemType e;
                int pos;
                int nInsertOption = -1;
                while(nInsertOption)
                {
                    printf("请输入要插入的元素位置和元素的值:");
                    scanf("%d %d", &pos, &e);
                    int ret = ListInsert_DuL(L, pos, e);
                    // 线性表未初始化,中断循环,回到主界面
                    if(ret == -1)
                        break;
                    // 插入位置不合法,结束本次循环,继续下次循环
                    else if(!ret)
                    {
                        printf("\n");
                        continue;
                    }
                    // 插入元素
                    printf("\n现在线性表为:");
                    ListTraverse_DuL(L, visit);
                    printf("\n1.是  0.否  是否继续: ");
                    scanf("%d", &nInsertOption);
                }
            }
            break;
        case 9:// 删除某个元素
            {
                int nDeleteOption = -1;
                while(nDeleteOption!=0 && nDeleteOption!=11)
                {
                    printf("1.按位置删除\t  2.按元素删除\t  11.回到主菜单\t  0.退出删除\n请选择你的操作:");
                    scanf("%d", &nDeleteOption);
                    switch(nDeleteOption)
                    {
                    case 1: // 1.按位置删除
                        {
                            ElemType e;
                            int pos;
                            printf("请输入要删除的位置:");
                            scanf("%d", &pos);
                            int ret = ListDelete_DuL(L, pos, e);
                            if (ret == -1) {
                                printf("\n");
                                break;
                            } else if (ret == 0) {
                                printf("删除位置不正确!\n");
                                break;
                            }
                            printf("现在线性表为:");
                            ListTraverse_DuL(L, visit);
                            printf("\n");
                        }
                        break;
                    case 2: // 2.按元素删除
                        {
                            printf("请输入要删除的元素:");
                            ElemType e;
                            scanf("%d", &e);
                            // 删除的将是第一个出现的元素
                            int pos = LocateElem_DuL(L, e, compare);
                            if (pos == -1) {
                                printf("\n");
                                break;
                            } else if (pos == 0) {
                                printf("没有值为%d的元素。", e);
                                break;
                            }
                            printf("值为%d是表中的第%d个元素。", e, pos);
                            ListDelete_DuL(L, pos, e);
                            printf("\n现在线性表为:");
                            ListTraverse_DuL(L, visit);
                            printf("\n");
                        }
                        break;
                    case 11:// 11.回到主菜单
                        mainMenu();
                        break;
                    case 0:// 0.退出查找
                        break;
                    default:
                        printf("请输入正确的数字!!!\n");
                        break;
                    }
                }
            }
            break;
        case 10:// 遍历线性表
            printf("遍历线性表:");
            ListTraverse_DuL(L, visit);
            break;
        case 11:// 回到主菜单
            mainMenu();
            break;
        case 0:// 退出
            break;
        default:
            printf("请输入正确的数字!!!");
            break;
        }
    }
    return 0;
}

上一篇:iOS开发数据库篇—FMDB简单介绍


下一篇:数据结构面试之一——单链表常见操作