【霍罗维兹数据结构】单链表 | 动态链接的栈和队列 | 多项式 - POLYNOMIALS | 一些链表的操作

简介: 【霍罗维兹数据结构】单链表 | 动态链接的栈和队列 | 多项式 - POLYNOMIALS | 一些链表的操作

前言:

最近在读霍罗维兹的《数据结构基础》(Fundamentals of Data Structures in C),本篇博客为阅读笔记和知识总结。


Ⅰ. 重温一些指针的知识

Sequential representation

将数据对象的连续元素以固定的距离存储起来。

足以满足许多操作。

But difficulties occurs when

但是,当插入和删除一个任意元素时就会出现困难(耗时)。

将几个大小不一的列表存储在最大尺寸的不同数组中(浪费存储)

将列表维持在一个数组中(频繁的数据移动)

Linked representation

链接表示一个节点,与列表中的一个元素相关,包含一个数据成分和一个指向列表中下一个项目的指针,这些指针通常被称为链接。

C提供了对指针的广泛支持,指针类型的实际值是内存的一个地址。

引用操作符 &  

解引用操作符 *

int i, *pi;
pi = &i;

为 i 赋值:

i = 10;
*pi = 10;

C允许我们对指针进行算术运算和关系运算。此外,我们还可以将指针明确地转换为整数。

空指针,是不指向任何对象或函数。

通常情况下,空指针由整数0表示。有一个叫做NULL的宏,它被定义为这个常数。

ANSI C,该宏定义在 stddef.h 中;K&R C中,该宏定义在stdio.h中。

// 测试空指针
if (pi == NULL) or if (!pi)

Pointers Can Be Dangerous

通过使用指针,我们可以达到高度的灵活性和效率。

但指针也有危险:访问意外的内存位置 当指针实际上没有指向一个对象时,将所有指针设置为NULL。

在指针类型之间转换时,明确地进行类型转换。

pi = malloc(sizeof(int)); /* assign to pi a pointer to int */
pf = (float *) pi; /* casts an int pointer to a float pointer */

为函数定义明确的返回类型。

Using Dynamically Allocated Storage

malloc / free

[Program 4.1]

int i, *pi;
float f, *pf;
pi = (int *) malloc(sizeof(int));
pf = (float *) malloc(sizeof(float));
*pi = 1024;
*pf = 3.14;
printf("an integer = %d, a float = %f\n", *pi, *pf);
free(pi);
free(pf);
inserting pf = (float *) malloc(sizeof(float));

Ⅱ.  单链表

链表中第一个节点的指针的名称就是列表的名称。

值得注意的是:① 节点不在连续的位置上。 ② 节点的位置可能在不同的运行中发生变化。

当我们写一个与链表有关的程序时,除了测试链表的结尾外,我们几乎不会去找一个特定的地址。

要在 cat 和 sat 之间插入 mat 这个词,我们必须:

(1) 获得一个目前未使用的节点;让它的地址为paddr。

(2) 将这个节点的数据字段设置为mat。

(3) 设置paddr的链接字段指向包含cat的节点的链接字段中找到的地址。

(4) 将包含cat的节点的链接字段设置为指向paddr的指针。

要在 cat 和 sat 之间插入mat这个词,我们必须:

(1) 获得一个目前未使用的节点;让它的地址为paddr。

(2) 将这个节点的数据字段设置为mat。

(3) 设置paddr的链接字段指向包含cat的节点的链接字段中找到的地址。

(4) 将包含cat的节点的链接字段设置为指向paddr的指针。

从列表中删除mat:

(1) 找到紧接在mat前面的元素(节点),也就是cat。

(2) 设置cat的链接字段为指向mat的链接字段。

Necessary capabilities to make linked list possible :

(1) 一个定义节点结构的机制,自引用结构。

(2) 一种在我们需要时创建新节点的方法,malloc。

(3) 一种移除我们不再需要的节点的方法,free

Example 4.1 [List of words ending in at]

// Necessary declarations are :
typedef struct list_node *list_pointer;
typedef struct list_node {
    char data[4];
    list_pointer link;
};
list_pointer ptr = NULL; /* creating a new empty list */

用于测试链表是否为空的宏 - A macro to test for an empty list

#define IS_EMPTY(ptr) (!(ptr))

创建新节点 - Creating new nodes

可以用 stdio.h 中的 malloc

ptr = (list_pointer) malloc (sizeof(list_node));

Assigning the values to the fields of the node

e 是一个指向包含字段名的结构的指针,e->name 可以当作是 (*e).name 的简写。

To place the word bat into the list

strcpy (ptr->data, "bat");
ptr->link = NULL;

Example 4.2 [Two-node linked list] :

typedef struct list_node* list_pointer;
typedef struct list_node {
  int data;
  list_pointer link;
};
list_pointer ptr = NULL;

[Program 4.2]

list_pointer create2()
{
  /* create a linked list with two nodes */
  list_pointer first, second;
  first = (list_pointer)malloc(sizeof(list_node));
  second = (list_pointer)malloc(sizeof(list_node));
  second->link = NULL;
  second->data = 20;
  first->data = 10;
  first->link = second;
  return first;
}

Example 4.3 [List insertion] :

在某个任意节点之后插入一个数据域为50的节点。 注意,我们使用参数声明 list_pointer *ptr。

我们写一个新的宏,IS_FULL,用来判断是否使用了已用的内存。

#define IS_FULL (ptr) (!(ptr))

[Program 4.3]

void insert(list_pointer* ptr, list_pointer node)
{
  /* insert a new node with data=50 into the list ptr after node */
  list_pointer temp;
  temp = (list_pointer)malloc(sizeof(list_node));
  if (IS_FULL(temp)) {
    fprintf(stderr, "The memory is full\n");
    exit(1);
  }
  temp->data = 50;
  if (*ptr) {
    temp->link = node->link;
    node->link = temp;
  }
  else {
    temp->link = NULL;
    *ptr = temp;
  }
}

Example 4.4 [List deletion]

删除取决于要删除的节点的位置。

设有三个指针:

ptr:指向列表的开始。

node:指向我们要删除的节点。

trail:指向要删除的节点之前的那个节点。

[Program 4.4]

void delete(list_pointer* ptr, list_pointer trail, list_pointer node)
{
  /* delete node from the list, trail is the preceding node
  ptr is the head of the list */
  if (trail)
    trail->link = node->link;
  else
    *ptr = (*ptr)->link;
  free(node);
}

Example 4.5 [Printing out a list] :

[Program 4.5]

void print_list(list_pointer ptr)
{
  printf("The list contains: ");
  for (; ptr; ptr = ptr->link)
    printf("%4d", ptr->data);
  printf("\n");
}
list_pointer search(list_pointer ptr, int num)
{
  for (; ptr; ptr = ptr->link)
    if (ptr->data == num) return ptr;
  return ptr;
}
void merge(list_pointer x, list_pointer y, list_pointer* z)
{
  list_pointer last;
  last = (list_pointer)malloc(sizeof(list_node));
  *z = last;
  while (x && y) {
    if (x->data <= y->data) {
      last->link = x;
      last = x;
      x = x->link;
    }
    else {
      last->link = y;
      last = y;
      y = y->link;
    }
  }
  if (x) last->link = x;
  if (y) last->link = y;
  last = *z; *z = last->link; free(last);
}

Ⅲ.  动态链接的栈和队列

如果我们只有一个栈或一个队列,我们可以用顺序表。

但是当几个栈和队列并存时,我们就需要动态链接他们。

注意,栈和队列的链接方向都是便于节点的插入和删除的。

#define MAX_STACKS 10 /* maximum number of stacks */
typedef struct {
  int key;
  /* other fields */
} element;
typedef struct stack* stack_pointer;
typedef struct stack {
  element item;
  stack_pointer link;
};
stack_pointer top[MAX_STACKS];

initialize empty stacks

top[i] = NULL, 0<=i<MAX_STACKS

the boundary conditions

top[i] == NULL iff the i th stack is empty
IS_FULL(temp) iff the memory is full

[Program 4.6]

void add(stack_pointer* top, element item)
{
  /* add an element to the top of the stack */
  stack_pointer temp = (stack_pointer)malloc(sizeof(stack));
  if (IS_FULL(temp)) {
    fprintf(stderr, "The memory is full\n");
    exit(1);
  }
  temp->item = item;
  temp->link = *top;
  *top = temp;
}

call : add(&top[stack_no], item);

[Program 4.7]

element delete(stack_pointer* top)
{
  /* delete an element from the stack */
  stack_pointer temp = *top;
  element item;
  if (IS_EMPTY(temp)) {
    fprintf(stderr, "The stack is empty\n");
    exit(1);
  }
  item = temp->item;
  *top = temp->link;
  free(temp);
  return item;
}

call : item = delete(&top[stack_no]);

#define MAX_QUEUES 10 /* maximum number of queues */
typedef struct {
  int key;
  /* other fields */
} element;
typedef struct queue* queue_pointer;
typedef struct queue {
  element item;
  queue_pointer link;
};
queue_pointer front[MAX_QUEUES], rear[MAX_QUEUES];

initialize empty queues

front[i] = NULL, 0<=i<MAX_QUEUES

the boundary conditions

front[i] == NULL iff the i th queue is empty
IS_FULL(temp) iff the memory is full

[Program 4.8] call : addq(&front[queue_no], &rear[queue_no], item);

void addq(queue_pointer* front, queue_pointer* rear, element item)
{
  /* add an element to the rear of the queue */
  queue_pointer temp = (queue_pointer)malloc(sizeof(queue));
  if (IS_FULL(temp)) {
    fprintf(stderr, "The memory is full\n");
    exit(1);
  }
  temp->item = item;
  temp->link = NULL;
  if (*front) (*rear)->link = temp;
  else *front = temp;
  *rear = temp;
}

[Program 4.9] call : item = deleteq(&front[queue_no]);

element deleteq(queue_pointer* front)
{
  /* delete an element from the queue */
  queue_pointer temp = *front;
  element item;
  if (IS_EMPTY(*front)) {
    fprintf(stderr, "The queue is empty\n");
    exit(1);
  }
  item = temp->item;
  *front = temp->link;
  free(temp);
  return item;
}

Ⅳ.  多项式 - POLYNOMIALS

0x00  Representing Polynomials As Singly Linked Lists

typedef struct poly_node* poly_pointer;
typedef struct poly_node {
  float coef;
  int expon;
  poly_pointer link;
};
poly_pointer a, b, d;

0x01  Adding Polynomials

[Program 4.10]

poly_pointer padd(poly_pointer a, poly_pointer b)
{
  /* return a polynomial which is the sum of a and b */
  poly_pointer front, rear, temp;
  float sum;
  rear = (poly_pointer)malloc(sizeof(poly_node));
  if (IS_FULL(rear)) {
    fprintf(stderr, “The memory is full\n”);
    exit(1);
  }
  front = rear;
  while (a && b)
    switch (COMPARE(a->expon, b->expon)) {
    case –1: /* a->expon < b->expon */
      attach(b->coef, b->expon, &rear);
      b = b->link; break;
    case 0: /* a->expon = b->expon */
      sum = a->coef + b->coef;
      if (sum) attach(sum, a->expon, &rear);
      a = a->link; b = b->link; break;
    case 1: /* a->expon > b->expon */
      attach(a->coef, a->expon, &rear);
      a = a->link;
    }
  /* copy rest of list a then list b */
  for (; a; a = a->link) attach(a->coef, a->expon, &rear);
  for (; b; b = b->link) attach(b->coef, b->expon, &rear);
  rear->link = NULL;
  /* delete extra initial node */
  temp = front; front = front->link; free(temp);
  return front;
}

[Program 4.11]

void attach(float coefficient, int exponent, poly_pointer* ptr)
{
  /* create a new node with coef = coefficient and
  expon = exponent, attach it to the node pointed to
  by ptr. ptr is updated to point to this new node */
  poly_pointer temp;
  temp = (poly_pointer)malloc(sizeof(poly_node));
  if (IS_FULL(temp)) {
    fprintf(stderr, "The memory is full\n");
    exit(1);
  }
  temp->coef = coefficient;
  temp->expon = exponent;
  (*ptr)->link = temp;
  *ptr = temp;
}

分析 padd

和之前说的多项式的padd类似

(1) 添加系数 (2) 指数比较 (3) 为d创建新节点

时间复杂度为

0x02  Erasing Polynomials

假设我们正在编写一个函数集合,用于输入、加法、减法和多项式的乘法,用链表。

注意,我们创建多项式 temp(x) 只是为了保存 d(x) 的部分结果。

通过返回 emp(x) 的节点,我们可以用它们来保存其他多项式。

[Program 4.12]

void erase(poly_pointer* ptr)
{
  /* erase the polynomial pointed by ptr */
  poly_pointer temp;
  while (*ptr) {
    temp = *ptr;
    *ptr = (*ptr)->link;
    free(temp);
  }
}

0x03  Representing Polynomials As Circularly Linked Lists

为了更有效地释放一个多项式的所有节点,我们修改了我们的链表结构,使最后一个节点的链接字段指向列表中的头节点。

我们想释放不再使用的节点,以便我们以后可以重新使用这些节点。 我们可以通过维护我们自己的已被 "释放 "的节点的列表(作为一个链),获得一个高效的循环链表 erase 的算法。 当我们需要一个新的节点时,我们检查这个列表。 如果这个列表不是空的,那么我们可以使用其中的一个节点。 只有当列表为空时,才使用 malloc 来创建一个新节点。 让 avail 成为一个 poly_pointer 类型的变量,指向已释放节点列表中的第一个节点。

[Program 4.13]

poly_pointer get_node(void) {
  /* provide a node for use */
  poly_pointer node;
  if (avail) {
    node = avail;
    avail = avail->link;
  }
  else {
    node = (poly_pointer)malloc(sizeof(poly_node));
    if (IS_FULL(node)) {
      fprintf(stderr, "The memory is full\n");
      exit(1);
    }
  }
  return node;
}

[Program 4.14]

void ret_node(poly_pointer ptr) {
  /* return a node to the available list */
  ptr->link = avail;
  avail = ptr;
}

[Program 4.15]

void cerase(poly_pointer* ptr) {
  /* erase the circular list ptr */
  poly_pointer temp;
  if (*ptr) {
    temp = (*ptr)->link;
    (*ptr)->link = avail;
    avail = temp;
    *ptr = NULL;
  }
}

我们必须把零多项式作为一种特殊情况来处理。 我们在每个多项式中引入一个头部节点。

对于有头节点表示的循环链表,我们可以从 cerase 中删除对 (*ptr) 的测试。

我们需要对padd做出的改变是:  

(1) Add two variables, starta = a and startb = b.
(2) Prior to the while loop, assign a = a->link and b = b->link.
(3) Change the while loop to while (a != starta && b != startb).
(4) Change the first for loop to for (; a != starta; a = a->link).
(5) Change the second for loop to for (; b != startb; b = b->link).
(6) Delete the lines :
rear -> link = NULL;
/* delete extra initial node */
(7) Change the lines :
temp = front;
front = front -> link;
free(temp);
to
rear -> link = front;
We may further simplify the addition algorithm
if we set the expon field of the head node to -1.

[Program 4.16]

poly_pointer cpadd(poly_pointer a, poly_pointer b)
{
  /* polynomials a and b are singly linked circular lists with a head
  node. Return a polynomial which is the sum of a and b */
  poly_pointer starta, d, lastd;
  int sum, done = FALSE;
  starta = a; /* record start of a */
  a = a->link; /* skip head node for a and b */
  b = b->link;
  d = get_node(); / (get a head node for sum* /
    d->expon = -1; lastd = d;
  do {
    switch (COMPARE(a->expon, b->expon)) {
    case –1: /* a->expon < b->expon */
      attach(b->coef, b->expon, &lastd);
      b = b->link; break;
    case 0: /* a->expon = b->expon */
      if (starta == a) done = TURE;
      else {
        sum = a->coef + b->coef;
        if (sum) attach(sum, a->expon, &lastd);
        a = a->link; b = b->link;
      }
      break;
    case 1: /* a->expon > b->expon */
      attach(a->coef, a->expon, &lastd);
      a = a->link;
    }
  } while (!done)
    lastd->link = d;
  return d;
}

Ⅴ.  其他操作

0x00  Operations For Chains

用于操作单链表的各种函数通常是必要的,也是可取的。我们已经看到了 get_node 和 ret_node。

我们使用如下声明:

typedef struct list_node* list_pointer;
typedef struct list_node {
  char data;
  list_pointer link;
};

0x01  反转链表 - Inverting a chain

如果我们使用三个指针,我们可以 "原地 "进行倒转。

[Program 4.17]

list_pointer invert(list_pointer lead)
{
  /* invert the list pointed to by lead */
  list_pointer middle, trail;
  middle = NULL;
  while (lead) {
    trail = middle;
    middle = lead;
    lead = lead->link;
    middle->link = trail;
  }
  return middle;
}

0x02  链接两个链表 - Concatenating two chains

[Program 4.18]

list_pointer concatenate(list_pointer ptr1, list_pointer ptr2)
{
  /* produce a new list that contains the list ptr1 followed
  by the list ptr2. The list pointed to by ptr1 is changed
  permanently */
  list_pointer temp;
  if (IS_EMPTY(ptr1)) return ptr2;
  else {
    if (!IS_EMPTY(ptr2)) {
      for (temp = ptr1; temp->link; temp = temp->link)
        ;
      temp->link = ptr2;
    }
    return ptr1;
  }
}

Ⅵ.  循环链表的操作

在一个循环链表的前面插入一个新节点。由于我们必须改变最后一个节点的链接字段,我们必须在链表中向后移动,直到找到最后一个节点。 如果循环链表的名称指向的是最后一个节点,而不是第一个节点,那就会更方便。

[Program 4.19]

void insert_front(list_pointer* ptr, list_pointer node)
/* insert node at the front of the circular list ptr,
where ptr is the last node in the list. */
{
  if (IS_EMPTY(*ptr)) {
    /* list is empty, change ptr to point to new entry */
    *ptr = node;
    node->link = node;
  }
  else {
    /* list is not empty, add new entry at front */
    node->link = (*ptr)->link;
    (*ptr)->link = node;
  }
}

在循环链表的后部插入一个新的节点:

我们只需要在 insert_front 的 else 子句中添加语句 *ptr = node 即可。

[Program 4.20]

int length(list_pointer ptr)
{
  /* find the length of the circular list ptr */
  list_pointer temp;
  int count = 0;
  if (ptr) {
    temp = ptr;
    do {
      count++;
      temp = temp->link;
    } while (temp != ptr);
  }
  return count;
}

参考资料:

Fundamentals of Data Structures in C

本章完。

相关文章
|
29天前
|
存储 缓存 算法
数据结构-链表(一)
链表(Linked List)是一种常见的数据结构,用于存储和组织数据。与数组不同,链表的元素(节点)在内存中不必连续存储,而是通过指针链接在一起。 链表由多个节点组成,每个节点包含两部分:数据(存储实际的元素值)和指针(指向下一个节点的引用)。链表的第一个节点称为头节点,最后一个节点称为尾节点,尾节点的指针通常指向空值(null)。
31 1
|
30天前
|
存储 算法 索引
数据结构与算法:单链表
朋友们大家好,本节来到数据结构与算法的新内容:单链表 在上篇文章中,我们知道顺序表通常需要预分配一个固定大小的内存空间, 通常以二倍的大小进行增容,可能会造成空间的浪费,本篇文章我们介绍的链表可以解决这个问题
|
1月前
|
存储
【单链表】数据结构单链表的实现
【单链表】数据结构单链表的实现
|
2天前
|
存储
线性表、链表、栈和队列的初始化
线性表、链表、栈和队列的初始化
9 1
|
9天前
|
存储 算法
单链表——“数据结构与算法”
单链表——“数据结构与算法”
|
11天前
|
存储 算法 调度
数据结构期末复习(3)栈和队列
数据结构期末复习(3)栈和队列
18 0
|
12天前
数据结构—链表(超详细)(山东大学)(数据结构实验三)
数据结构—链表(超详细)(山东大学)(数据结构实验三)
|
14天前
数据结构--链表刷题(一)快慢指针(上)
数据结构--链表刷题(一)快慢指针
16 0
|
23天前
|
算法 C语言
【算法与数据结构】 C语言实现单链表队列详解2
【算法与数据结构】 C语言实现单链表队列详解
|
23天前
|
存储 算法 C语言
【算法与数据结构】 C语言实现单链表队列详解1
【算法与数据结构】 C语言实现单链表队列详解