【霍罗维兹数据结构】单链表 | 动态链接的栈和队列 | 多项式 - 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

本章完。

相关文章
|
7月前
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
215 1
|
5月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
98 0
栈区的非法访问导致的死循环(x64)
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
10月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
448 77
|
9月前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
215 11
|
9月前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
10月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
374 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
10月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
222 7
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
991 9
|
12月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
262 59

热门文章

最新文章