C语言之双向链表

一、双向链表

在上一篇文章我们已经介绍了单向链表,了解了其具体构造及其基本操作,我们也能发现其部分缺点,查找数据和删除数据的时候都必须从链表头指针开始挨个遍历,直到找到目标节点。

双向链表的双向指的的就是我们遍历链表的时候可以正序遍历也可逆序遍历,链表内的节点不止与下一个节点有关系,与上一个节点同样有关系。具有两个方向。

1.1双向链表的构造如下图所示

根据图我们可以看到,双向链表和单向链表一样拥有数据域和指针域,但是区别在于双向链表的指针域有两个指针,分别为next和prev。其中next是指向下一个节点的指针,prev是指向上一个节点的指针。

 

二、双向链表的基本操作

首先我们来看看双向链表元素的数据结构

typedef struct _Link_t
{
  int data;                   //节点保存的数据
  struct _Link_t *next;       //指向下一个节点的指针
  struct _Link_t *prev;	      //指向上一个节点的指针
} LINK_T;

对比单向链表的数据结构,双向链表元素结构与单向链表不同点在于其多了一个用于指向上一个节点的指针成员。

2.1创建节点

LINK_T *Create_Node(int data)
{
  LINK_T *node;

  node = (LINK_T*)malloc(sizeof(LINK_T));
  memset(node, 0, sizeof(LINK_T));
  node->next = NULL;
  node->prev = NULL;
  node->data = data;
  
  return node;

}

该部分内容与单向链表基本一致。

2.2链表插入

从链表头插入如下图所示

在这里我们需要注意一个细节,就是头指针后面有没有节点。

假设头指针后面没有节点,则插入新节点后,新节点的下一个节点的prev指针就不用指向新节点,没有下一个节点。

假设头指针后面有节点,则插入新节点后,新节点的下一个节点的prev指针就需要指向新节点。

下面我们看看实际代码:

void Insert_Top(LINK_T *head, LINK_T *node)
{
  LINK_T *p = head;

  node->next = p->next;                   //首先新节点的next指针指向头指针中next的指向
  if (NULL != p->next)                    //判断头指针后面还有没有节点
   p->next->prev = node;                  //如果有的话,新节点的下一个节点的prev指针指向新节点
                                       
  node->prev = p;                         //新节点的prev指针指向头指针
  p->next = node;                         //头指针的next指向新节点

}

链表尾部插入节点:

由图分析,我们可知双向链表尾部插入操作与单向链表从尾部插入无异,只需要注意加上prev指针的指向即可。

具体代码如下所示:

void Insert_Tail(LINK_T *head, LINK_T *node)
{
  LINK_T *p = head;

  while((p->next != NULL) && (p = p->next));      //找到尾部

  node->prev = p;
  p->next = node;

}

2.3节点的遍历

正序遍历代码如下:

void Traverse_P(LINK_T *head)
{

  LINK_T *p = head;

  do
  {
    printf("%d  ", p->data);
  }
  while((NULL != p->next) && (p = p->next));

  printf("\n");

}

逆序遍历代码如下:

void Traverse_R(LINK_T *head)
{
  LINK_T *p = head;

  while((NULL != p->next) && (p = p->next));

  do
  {
    printf("%d  ", p->data);
  }
  while((NULL != p ->prev) && (p = p->prev));

  printf("\n");
}

该程序是找到链表最后一个节点再去反向遍历。

 

三、基本例程

例程功能实现,先将数组data的数据按序存到每个节点中,然后正序遍历并且打印数据,再逆序遍历并且打印数据以验证各调用函数的正确性。

在该程序中,头指针的数据域的数据代表链表的节点数。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


typedef struct _Link_t
{
  int data;
  struct _Link_t *next;
  struct _Link_t *prev;	
} LINK_T;


LINK_T *Create_Node(int data);//创建节点
void Insert_Tail(LINK_T *head, LINK_T *node);//尾插入节点
void Insert_Top(LINK_T *head, LINK_T *node);//头插入节点
void Traverse_P(LINK_T *head);//正序遍历
void Traverse_R(LINK_T *head);//逆序遍历


int main(int argc, const char *argv[])
{
  int i = 0;
  int data[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};


  LINK_T *head;
  head = (LINK_T*)malloc(sizeof(LINK_T));
  memset(head, 0, sizeof(LINK_T));
  head->prev = NULL;

  for (i = 0; i < 10; i++)
  { 
    Insert_Top(head, Create_Node(data[i]));
    head->data++;
  }

  Traverse_P(head);
  Traverse_R(head);
  return 0;
}

LINK_T *Create_Node(int data)
{
  LINK_T *node;
  node = (LINK_T*)malloc(sizeof(LINK_T));
  memset(node, 0, sizeof(LINK_T));
  node->next = NULL;
  node->prev = NULL;
  node->data = data;
  
  return node;

}


void Insert_Tail(LINK_T *head, LINK_T *node)
{
  LINK_T *p = head;

  while((p->next != NULL) && (p = p->next));

  node->prev = p;
  p->next = node;

}

void Insert_Top(LINK_T *head, LINK_T *node)
{
  LINK_T *p = head;

  node->next = p->next;
  if (NULL != p->next)
   p->next->prev = node;

  node->prev = p;
  p->next = node;

}


void Traverse_P(LINK_T *head)
{

  LINK_T *p = head;

  do
  {
    printf("%d  ", p->data);
  }
  while((NULL != p->next) && (p = p->next));

  printf("\n");

}

void Traverse_R(LINK_T *head)
{
  LINK_T *p = head;

  while((NULL != p->next) && (p = p->next));

  do
  {
    printf("%d  ", p->data);
  }
  while((NULL != p ->prev) && (p = p->prev));

  printf("\n");
}

程序执行结果如下:

10  10  9  8  7  6  5  4  3  2  1  
1  2  3  4  5  6  7  8  9  10  10 

 仓促成文,不当之处,尚祈方家和读者批评指正。联系邮箱[email protected]

发布了12 篇原创文章 · 获赞 5 · 访问量 640

猜你喜欢

转载自blog.csdn.net/qq_35600620/article/details/103926644