前言:
最近在读霍罗维兹的《数据结构基础》(Fundamentals of Data Structures in C),本篇博客为阅读笔记和知识总结。
目录
0x00 Representing Polynomials As Singly Linked Lists
0x03 Representing Polynomials As Circularly Linked Lists
0x02 链接两个链表 - Concatenating two chains
Ⅰ. 重温一些指针的知识
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
本章完。