C语言数据结构【手抄版】
注释:文中可复制代码均在Visual Studio 2022编译器中编写,图片中代码仅为理论代码,非实操代码!
第二章 线性表
2.1.线性表的定义和基本运算
2.1.1.线性表的逻辑定义
线性表(Linear[ /ˈlɪniə®/ ]_List)是最简单和最常用的一种数据结构,它是由n个数据元素(结点)a1,a2,…,an组成的有限序列。其中,数据元素的个数n为表的长度。当n为0时称为空表,非空的线性表通常记为:
(a1,a2,…,ai-1, ai, ai+1, …, an)
这里的元素ai(1≦i≦n)是一个抽象的符号,它可以是一个数或者一个符号,还可以是一个较复杂的记录。如一个学生、一本书等信息就是一个数据元素,它可以由若干个数据项组成。
例如,有学生基本情况如表2.1所示。
表2.1中每个学生的情况为一个数据元素(记录),它由学号、姓名、性别、年龄和籍贯等5个数据项组成。
从线性表的定义可以看出它的逻辑特征,对于一个非空的线性表:
①有且仅有一个称为开始元素的a1,它没有前趋,仅有一个直接后继a2;
②有且仅有一个称为终端元素的an,它没有后继,仅有一个直接前趋;
③其余元素ai(2≦i≦n-1)称为内部元素,它们都有且仅有一个直接前趋ai-1和一个直接后继ai+1。
线性表中元素之间的逻辑关系就是上述的相邻关系,又称为线性关系,可见线性表是一种典型的线性结构。
2.1.2.线性表的基本运算(★★★)
在第一章中已提到,数据的运算是定义在逻辑结构上的,而运算的具体实现则是在存储结构上进行的。因此,在逻辑结构上定义的运算,只要给出这些运算的功能是“做什么“,至于”怎么做“等实现细节,只有待确定了存储结构之后才能考虑。对于线性表,常见的基本运算有以下几种:
(1)置空表InitList(L),构造一个空的线性表L。
(2)求表长ListLength(L),返回线性表L中元素个数,即表长。
(3)取表中第i个元素GetNode(L,i),若1≦i≦ListLength(L),则返回第i个元素ai。
(4)按值查找LocateNode(L,x),在表L中查找第一个值为x的元素,并返回该元素在表L中的位置,若表中没有元素的值为x,则返回0值。
(5)插入InsertList(L,i,x),在表L的第i元素之前插入一个值为x的新元素,表L的长度加1。
(6)删除DeleteList(L,i),删除表L的第i个元素,表L的长度减1。
上述运算仅仅是线性表的基本运算,不是其全部运算。因为对不同问题的线性表,所需要的运算可能不同。因此,对于实际问题中涉及其它更为复杂的运算,可用基本运算的组合来实现。
【例2.1】假设有两个线性表LA和LB分别表示两个集合A和B,现要求一个新集合A=A∪B(集合的并集)。
其算法思想:扩大线性表LA,将表LB中不在LA中出现的元素插入到LA中。只要从线性表LB中依次取出每个元素,按值在线性表LA中查找,若没查到则插入之。其算法描述如下:
【例2.2】删除线性表L中重复的元素。
实现该功能的算法思想是:从表L的第一个元素(i=1)开始,逐个检查i位置以后的任一位置j,若两元素值相同,则从表中删除第j个元素, …,直到i移到当前表L的最后一个位置为止。其算法描述如下:
2.2.线性表的顺序存储和基本运算的实现
2.2.1.线性表的顺序存储
线性表的顺序存储指的是将线性表的数据元素按其逻辑次序依次存入一组地址连续的存储单元里,用这种方法存储的线性表称为顺序表。
假设线性表中所有元素的类型是相同的,且每个元素需要占用d个存储单位,其中第一个单元的存储位置(地址)就是该元素的存储位置。那么,线性表中第i+1个元素的存储位置LOC(ai+1)和第i个元素的存储位置LOC(ai)有关系:
LOC(ai+1)=LOC(ai) + d
一般来说,线性表的第i个元素ai的存储位置为:
LOC(ai)=LOC(a1) + (i-1)*d
其中,LOC(a1)是线性表的第一个元素a1的存储位置,通常称之为基地址。
线性表的这种机内表示称为线性表的顺序存储结构。它的特点是,元素在表中的相邻关系,在计算机内也存在着相邻的关系。每个元素ai的存储地址是该元素在表中的位置i的线性函数,只要知道基地址和每个元素占用的单元数(元素的大小),就可求出任一元素的存储地址。因此,只要确定了线性表存储的起始位置,线性表中任意一个元素都可随机存取,所以顺序表是一种随机存取结构。
由于高级程序设计语言中的数组类型具有随机存取的特性,因此,通常用数组来描述顺序表。另外,除了存储线性表的节点外,还需要一个变量来标识线性表的当前长度,所以用下面的结构类型来定义顺序表类型。
在上述定义中,线性表元素存储空间的大小ListSize应慎重选择其值,使得它既能满足表结点的数目动态增加的需求,又不至于因定义过大而浪费存储空间。
顺序表在内存中的存储表示如图2.1所示,图中的存储地址b为基地址LOC(a1)。因为C语言的数组下标是从0开始的,因此使用时要特别注意。假设L是SeqList类型的顺序表,则开始结点a1存放在L.data[0]中,终端结点an存放在L.data[n-1]中。同理,若p为一个指向顺序表的指针变量,则a1和an分别存放在p->data[0]和>data[n-1]中。
2.2.2.顺序表上基本运算的实现
在定义了线性表的顺序结构之后,就可以讨论线性表的基本运算在该存储结构上如何实现了。在顺序表上,有些运算是很容易实现的。例如,若表L是SeqList类型的顺序表,那么随机存取表中第i个结点,只需要用L.data[i-1]就可以直接访问;若需要置空表(初始化)操作,仅需要将当前表长置成0,即L.length==0。下面将重点讨论顺序表的插入和删除两种基本运算的实现方法。
1. 插入运算
线性表的插入运算是指在线性表的第i-1个元素和第i个元素之间插入一个新元素x,使长度为n的线性表:
(a1,a2,…,ai-1, ai, ai+1, …, an)
变为长度为n+1的线性表:
(a1,a2,…,ai-1, x, ai, ai+1, …, an)
由于线性表逻辑上相邻的元素在物理结构上也是相邻的,因此在插入一个新元素之后,线性表的逻辑关系也发生了变化,其物理存储关系也要发生相应的变化。除非i=n+1,否则必须将原线性表的第i,i+1、…、n个元素分别向后移动一个位置,空出第i个位置以便插入新元素x。其插入过程中顺序表L.data[]的变化情况如表2.2所示。表中第一行表示原表元素的存储结构,第二行是后移之后的存储表示,而第三行则是插入x后的元素存储情况。
插入算法描述如下:
从上述的算法以及插入过程可以看出,一般情况下,在第i(1≦i≦n)个元素之前插入一个新的元素时,需要进行n-i+1次移动。而该算法的执行时间主要花在for循环的元素后移上,因此该算法的时间复杂度不仅依赖于表的长度n,而且还与元素的插入位置i有关。当i=n+1时,for循环一次也不执行,无需移动元素,属于最好情况,其时间复杂度为O(1);当i=1,循环需要执行n次,即需要移动表中所有元素,属于最坏情况,算法时间复杂度为O(n)。由于插入元素可在表的任何位置上进行,因此需要分析讨论算法的平均移动次数。
假设pi是在第i个元素之前插入一个元素的概率,则在长度为n的线性表中插入一个节点时所需要移动元素次数的期望值(平均次数)为
不失一般性,假定在线性表的任何位置上插入元素的机会时相等的,即是等概率的,则有
p1=p2=…=pn+1=1/(n+1)
因此,在等概率的情况下插入,需要移动元素的平均次数为
Eis(n)恰好是表长的一半,当表长n较大时,该算法的效率相当低。因为Eis(n)是取决于问题规模n的,它是线性阶的,因此该算法的平均时间复杂度是O(n)。
例如,假定一个有序表A=(23,31,46,54,58,67,72,88),表长n=8。当向其中插入元素56时,此时i等于5,因此应插入到第i-1个位置上,从而需要将第i-1个元素及之后的所有元素都向后移动一位,将第i-1个元素位置空出来,插入新元素56。插入后的有序表为(23,31,46,54,56,58,67,72,88)。按上述移动次数的计算公式,可知本插入操作需要移动n-i+1=8-5+1=4次。
2. 删除运算
线性表的删除运算指的是将表中第i(1≦i≦n)个元素删除,使长度为n的线性表
(a1,a2,…,ai-1, ai, ai+1, …, an)
变成长度为n-1的线性表:
(a1,a2,…,ai-1, ai+1, …, an)
线性表的逻辑结构和存储结构都发生了相应变化,与插入运算相反,插入是向后移动元素,而删除运算则是向前移动元素,除非i=n时直接删除终端元素,不需移动元素。其删除过程中顺序表的变化情况如表2.3所示。表中第一行是原表存储结构,第二行则是删除第i个元素后的顺序表的存储情况。
由此,很容易写出删除第i个元素的算法:
该算法的时间复杂度分析与插入算法类似,删除一个元素也需要移动元素,移动的次数取决于表长n和位置i。当i=1时,则前移n-1次;当i=n时不需要移动,因此算法的时间复杂度为O(n)。由于算法中删除第i个元素是将从第i+1至第n个元素以此向前移动一个位置,共需要移动n-i个元素。同插入类似,假设在顺序表上删除任何位置上元素的机会相等,qi为删除第i个元素的概率,则删除一个元素的平均移动次数为
由此可以看出,在顺序表上做删除运算,平均移动元素次数约为表长的一半,因此该算法的平均时间复杂度为O(n)。
请注意:在上述算法中用到了退出函数exit(),该函数是C语言系统提供的程序非正常结束或正常结束时可退出整个程序的处理函数,exit(0)表示非正常退出,exit(1)表示正常退出。使用时,需要在源程序头部加上预处理命令:#include<stdlib.h>。该用法在后续的章节中还会出现。由于各算法中的DataType的实际类型不同,在程序出现执行错误时,不能给出一个确定的类型返回值,因此用exit()函数给出一个统一的返回值,并退出程序的执行。
3. 顺序表上的其它运算举例
【例2.3】已知一长度为n顺序存储的线性表,试写一算法将该线性表逆置。
分析:不妨设线性表为(a1,a2, …,an),逆置之后为(an,an-1, …,a1 ),并且表以顺序存储方式存储。实现其算法的基本思想是:先以表长的一半为循环控制次数,将表中最后一个元素同顺数第一个元素交换,将倒数第二个元素同顺数第二个元素交换, …,依此类推,直至交换完为止。为此可设计算法如下:
这个算法只需要进行数据元素的交换操作,其主要时间花在for循环上,显然整个算法的时间复杂度为O(n)。
【例2.4】试写一算法,实现在顺序表中找出最大值和最小值的元素及其所在位置。
分析:如果在查找最大值和最小值的元素时各扫描一遍所有元素,则至少要比较2n次,可以使用一次扫描找出最大和最小值的元素。另外,在算法中要求带回求得的最大值和最小值元素及其所在位置,可用4个指针变量参数间接得到,也可以用外部变量实现,因为函数本身只可返回一个值。下面是采用指针变量参数来实现的:
当调用该函数时,调用语句中对应于指针形参的实参应为变量的地址。该算法在线性表元素递减有序排列的最坏情况下, L.data[i]>*max条件均不成立,此时比较的次数为n-1 (设表长为n)次,另外每次都要比较L.data[j]<*min,同样需要比较n-1次,因此总的比较次数为2(n-1)。而最好的情况是表中元素是按递增有序排列的,这时(L.data[i]>*max)条件均成立,不会再执行else后的比较,可见总的比较次数为n-1。该算法的平均比较次数为: (2 (n-1) + (n-1)) /2=3(n-1)/2,可见,该算法的时间复杂度为O(n)。
4. 动态数组
基于顺序表构建一个动态数组,动态的在内存中扩充数组的长度。在C语言中,多个文件如果有相同的函数名称,会无法选择调用哪个函数,C语言不支持函数的重载。我们需要对已经编写好的C程序做一些小步的重构调整,以便于后面很方便的复用动态数组。
借鉴Java的封装思想,将动态数组的创建与运用过程封装起来不对外暴露,将对动态数组的创建与操作功能对外暴露,为了防止函数名称相同导致无法使用的问题,我们将对外暴露的操作功能封装到一个结构体当中,使用起来有点类似Java的对象使用方式。
编码采用Visual Studio 2022编译器编写,编写头文件dynamicArray.h,定义ArrayList对象(结构体),对外提供创建ArrayList对象的功能,在ArrayList内部封装动态数组的操作函数。
#pragma once
#ifndef DYNAMIC_ARRAY_H
#define DYNAMIC_ARRAY_H
struct ArrayList
{
struct DynamicArray* dynamicArray;
int (*insert)(int index, void* data, struct ArrayList* arrayList);
int (*addSort)(void* data, int (*sort)(void* data, void* ArrayData), struct ArrayList* arrayList);
void* (*get)(int index, struct ArrayList* arrayList);
void* (*getData)(void* dataValue, int (*compare)(void* firstData, void* secondData), struct ArrayList* arrayList);
int (*capacity)(struct ArrayList* arrayList);
int (*size)(struct ArrayList* arrayList);
void (*foreach)(void (*printArray)(void* data), struct ArrayList* arrayList);
char* (*toString)(struct ArrayList* arrayList); // deep copy | Return new memory, don't worry ruin the ArrayList.
void* (*removeByIndex)(int index, struct ArrayList* arrayList);
void* (*removeByData)(void* data, int (*compare)(void* firstData, void* secondData), struct ArrayList* arrayList);
void (*copy)(struct ArrayList* source, struct ArrayList* target, int index, int lenght); // shallow copy | if lenght = -1; lenght = size - index;
struct ClearArea* (*clear)(struct ArrayList* arrayList); // shallow copy with mutual exclusion ruin.
struct ClearArea* (*destroy)(struct ArrayList* arrayList); // shallow copy with mutual exclusion ruin.
void (*ruin)(struct ArrayList* arrayList);
};
struct ClearArea
{
void** array;
int size;
};
struct ArrayList* initArrayList(int capacity);
#endif
在dynamicArray.c中实现dynamicArray.h中声明的函数功能。
#define _CRT_SECURE_NO_WARNINGS //规避C4996告警
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "dynamicArray.h"
struct DynamicArray* initDynamicArray(int capacity);
int insertDynamicArray(int index, void* data, struct ArrayList* arrayList);
int addSortDynamicArray(void* data, int (*sort)(void* data, void* ArrayData), struct ArrayList* arrayList);
void* getDataByIndex(int index, struct ArrayList* arrayList);
void* getDataByValue(void* dataValue, int (*compare)(void* value, void* Data), struct ArrayList* arrayList);
int capacityDynamicArray(struct ArrayList* arrayList);
int sizeDynamicArray(struct ArrayList* arrayList);
void foreachDynamicArray(void (*printArray)(void* data), struct ArrayList* arrayList);
char* toStringDynamicArray(struct ArrayList* arrayList);
void* removeArrayByIndex(int index, struct ArrayList* arrayList);
void* removeArrayByData(void* data, int (*compare)(void* firstData, void* secondData), struct ArrayList* arrayList);
void copyDynamicArray(struct ArrayList* source, struct ArrayList* target, int index, int lenght);
struct ClearArea* clearDynamicArray(struct ArrayList* arrayList);
struct ClearArea* destroyDynamicArray(struct ArrayList* arrayList);
void ruinDynamicArray(struct ArrayList* arrayList);
struct ArrayList* initArrayList(int capacity)
{
struct ArrayList* arrayList = malloc(sizeof(struct ArrayList));
struct DynamicArray* dynamicArray = initDynamicArray(capacity);
if (arrayList == NULL || dynamicArray == NULL)
{
printf("初始化动态数组,创建动态数组空间失败!\n");
return NULL;
}
arrayList->dynamicArray = dynamicArray;
arrayList->insert = insertDynamicArray;
arrayList->addSort = addSortDynamicArray;
arrayList->get = getDataByIndex;
arrayList->getData = getDataByValue;
arrayList->capacity = capacityDynamicArray;
arrayList->size = sizeDynamicArray;
arrayList->foreach = foreachDynamicArray;
arrayList->toString = toStringDynamicArray;
arrayList->removeByIndex = removeArrayByIndex;
arrayList->removeByData = removeArrayByData;
arrayList->copy = copyDynamicArray;
arrayList->clear = clearDynamicArray;
arrayList->destroy = destroyDynamicArray;
arrayList->ruin = ruinDynamicArray;
return arrayList;
}
struct DynamicArray
{
void** array;
int capacity;
int size;
};
struct DynamicArray* initDynamicArray(int capacity)
{
if (capacity <= 0)
{
printf("初始化数组容量不能小于等于0,修改初始容量为5!\n");
capacity = 5;
}
struct DynamicArray* dynamicArray = malloc(sizeof(struct DynamicArray));
void** array = calloc(capacity, sizeof(void*));
if (dynamicArray == NULL || array == NULL)
{
printf("初始化动态数组,创建动态数组空间失败!\n");
return NULL;
}
dynamicArray->array = array;
dynamicArray->capacity = capacity;
dynamicArray->size = 0;
return dynamicArray;
}
int insertDynamicArray(int index, void* data, struct ArrayList* arrayList)
{
if (arrayList == NULL || arrayList->dynamicArray == NULL)
{
printf("动态数组不存在!\n");
return NULL;
}
struct DynamicArray* dynamicArray = arrayList->dynamicArray;
if (data == NULL)
{
printf("插入动态数组元素不存在!\n");
return 0;
}
// out of the MAX size.
if (index > INT_MAX)
{
printf("插入动态数组索引大于INT_MAX不合法!\n");
return 0;
}
// index out of size set the size.
if (index < 0 || index > dynamicArray->size)
{
index = dynamicArray->size;
}
// extended capacity.
if (dynamicArray->size >= dynamicArray->capacity)
{
dynamicArray->capacity = dynamicArray->capacity << 1;
void** newSpace = calloc(dynamicArray->capacity, sizeof(void*));
if (newSpace == NULL)
{
printf("动态数组扩容失败!\n");
return 0;
}
memcpy(newSpace, dynamicArray->array, sizeof(void*) * dynamicArray->size);
free(dynamicArray->array);
dynamicArray->array = newSpace;
}
// move element.
for (int i = dynamicArray->size - 1; i >= index; i--)
{
dynamicArray->array[i + 1] = dynamicArray->array[i];
}
dynamicArray->array[index] = data;
dynamicArray->size++;
return 1;
}
int addSortDynamicArray(void* data, int (*sort)(void* data, void* ArrayData), struct ArrayList* arrayList)
{
if (arrayList == NULL || arrayList->dynamicArray == NULL)
{
printf("动态数组不存在!\n");
return NULL;
}
struct DynamicArray* dynamicArray = arrayList->dynamicArray;
int index = dynamicArray->size;
for (int i = 0; i< dynamicArray->size; i++)
{
if (sort(data, dynamicArray->array[i]))
{
index = i;
break;
}
}
return arrayList->insert(index, data, arrayList);
}
void* getDataByIndex(int index, struct ArrayList* arrayList)
{
if (arrayList == NULL || arrayList->dynamicArray == NULL)
{
printf("动态数组不存在!\n");
return NULL;
}
struct DynamicArray* dynamicArray = arrayList->dynamicArray;
if (index < 0)
{
printf("获取动态数组数据元素失败,索引不能小于0!\n");
return NULL;
}
if (index > dynamicArray->size)
{
printf("获取动态数组数据元素失败,索引不能大于最大存储容量!\n");
return NULL;
}
return dynamicArray->array[index];
}
void* getDataByValue(void* dataValue, int (*compare)(void* value, void* Data), struct ArrayList* arrayList)
{
if (arrayList == NULL || arrayList->dynamicArray == NULL)
{
printf("动态数组不存在!\n");
return NULL;
}
struct DynamicArray* dynamicArray = arrayList->dynamicArray;
for (int i = 0; i < dynamicArray->size; i++)
{
if (compare(dataValue, dynamicArray->array[i])) {
return dynamicArray->array[i];
}
}
}
int capacityDynamicArray(struct ArrayList* arrayList)
{
if (arrayList == NULL || arrayList->dynamicArray == NULL)
{
printf("动态数组不存在!\n");
return NULL;
}
struct DynamicArray* dynamicArray = arrayList->dynamicArray;
return dynamicArray->capacity;
}
int sizeDynamicArray(struct ArrayList* arrayList)
{
if (arrayList == NULL || arrayList->dynamicArray == NULL)
{
printf("动态数组不存在!\n");
return NULL;
}
struct DynamicArray* dynamicArray = arrayList->dynamicArray;
return dynamicArray->size;
}
void foreachDynamicArray(void (*printArray)(void* data), struct ArrayList* arrayList)
{
if (arrayList == NULL || arrayList->dynamicArray == NULL)
{
printf("动态数组不存在!\n");
return NULL;
}
struct DynamicArray* dynamicArray = arrayList->dynamicArray;
if (printArray == NULL)
{
printf("动态数组遍历逻辑未定义!\n");
return;
}
for (int i = 0; i < dynamicArray->size; i++)
{
void* in = dynamicArray->array[i];
printArray(dynamicArray->array[i]);
}
}
char* toStringDynamicArray(struct ArrayList* arrayList)
{
if (arrayList == NULL || arrayList->dynamicArray == NULL)
{
printf("动态数组不存在!\n");
return NULL;
}
struct DynamicArray* dynamicArray = arrayList->dynamicArray;
char* string = malloc(sizeof(char) * dynamicArray->size + 1);
if (string == NULL)
{
printf("序列化动态数组时,序列化的数据没有足够的内存可以记录。");
return NULL;
}
for (int i = 0; i < dynamicArray->size; i++)
{
string[i] = *(char*)dynamicArray->array[i]; // deep copy
}
string[dynamicArray->size] = '\0';
return string;
}
void* removeArrayByIndex(int index, struct ArrayList* arrayList)
{
if (arrayList == NULL || arrayList->dynamicArray == NULL)
{
printf("动态数组不存在!\n");
return NULL;
}
struct DynamicArray* dynamicArray = arrayList->dynamicArray;
if (index < 0 || index > dynamicArray->size)
{
printf("动态数组删除位置%d不合法!\n", index);
return;
}
void* remove = dynamicArray->array[index];
for (int i = index; i < dynamicArray->size - 1; i++)
{
dynamicArray->array[i] = dynamicArray->array[i + 1];
}
// Empty the last velue
dynamicArray->array[dynamicArray->size - 1] = NULL;
dynamicArray->size--;
return remove;
}
void* removeArrayByData(void* data, int (*compare)(void* firstData, void* secondData), struct ArrayList* arrayList)
{
if (arrayList == NULL || arrayList->dynamicArray == NULL)
{
printf("动态数组不存在!\n");
return NULL;
}
struct DynamicArray* dynamicArray = arrayList->dynamicArray;
for (int i = 0; i < dynamicArray->size; i++)
{
if (compare(dynamicArray->array[i], data)) {
return removeArrayByIndex(i, arrayList);
}
}
printf("根据数据删除元素失败,没有在数组中找到匹配元素。\n");
}
void copyDynamicArray(struct ArrayList* source, struct ArrayList* target, int index, int lenght)
{
if (source == NULL || source->dynamicArray == NULL)
{
printf("源动态数组不存在!\n");
return NULL;
}
if (target == NULL || target->dynamicArray == NULL)
{
printf("目标动态数组不存在!\n");
return NULL;
}
if ((index < 0 || lenght < -1) && index > source->size(source))
{
printf("拷贝动态数组索引或长度不合法!\n");
return NULL;
}
if (lenght == -1) lenght = source->size(source) - index;
if (lenght == 0) return;
for (int i = 0; i < lenght; i++)
{
target->insert(i, source->get(index + i, source), target);
}
}
struct ClearArea* clearDynamicArray(struct ArrayList* arrayList)
{
if (arrayList == NULL || arrayList->dynamicArray == NULL)
{
printf("动态数组不存在!\n");
return NULL;
}
struct DynamicArray* dynamicArray = arrayList->dynamicArray;
struct ClearArea* clearArea = malloc(sizeof(struct ClearArea));
void** resultArray = malloc(sizeof(void*) * dynamicArray->size);
if (clearArea == NULL || resultArray == NULL)
{
printf("清空动态数组时,清空的数据没有足够的内存可以记录。");
return NULL;
}
clearArea->size = dynamicArray->size;
memcpy(resultArray, dynamicArray->array, sizeof(void*) * dynamicArray->size);
for (int i = 0; i < dynamicArray->capacity; i++)
{
if (dynamicArray->array[i] != NULL)
{
dynamicArray->array[i] = NULL;
}
}
dynamicArray->size = 0;
clearArea->array = resultArray;
return clearArea;
}
struct ClearArea* destroyDynamicArray(struct ArrayList* arrayList)
{
struct ClearArea* clearArea = clearDynamicArray(arrayList);
struct DynamicArray* dynamicArray = arrayList->dynamicArray;
if (dynamicArray->array != NULL)
{
free(dynamicArray->array);
dynamicArray->array = NULL;
}
free(dynamicArray);
dynamicArray = NULL;
if (arrayList == NULL)
{
printf("动态数组操作对象不存在!\n");
return NULL;
}
free(arrayList);
arrayList = NULL;
return clearArea;
}
void ruinDynamicArray(struct ArrayList* arrayList)
{
struct ClearArea* clearArea = destroyDynamicArray(arrayList);
for (int i = 0; i < clearArea->size; i++)
{
free(clearArea->array[i]);
clearArea->array[i] = NULL;
}
free(clearArea);
}
测试:
#define _CRT_SECURE_NO_WARNINGS //规避C4996告警
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "dynamicArray.h"
struct Person
{
char name[64];
int age;
};
void printArray(void* data)
{
struct Person* person = data;
printf("姓名:%s--年龄:%d\n", person->name, person->age);
}
int compare(void* firstData, void* secondData)
{
struct Person* firstPerson = firstData;
struct Person* secondPerson = secondData;
return strcmp(firstPerson->name, secondPerson->name) == 0 && firstPerson->age == secondPerson->age;
}
int main()
{
struct ArrayList* arrayList = initArrayList(0);
printf("新建动态数组长度%d\n", arrayList->size(arrayList));
printf("插入数据前,容量:%d 长度:%d\n", arrayList->capacity(arrayList), arrayList->size(arrayList));
struct Person p1 = { "唐三藏", 24 }; arrayList->insert(0, &p1, arrayList);
struct Person p2 = { "白龙马", 502 }; arrayList->insert(0, &p2, arrayList);
struct Person p3 = { "孙悟空", 650 }; arrayList->insert(1, &p3, arrayList);
struct Person p4 = { "猪八戒", 800 }; arrayList->insert(0, &p4, arrayList);
struct Person p5 = { "沙和尚", 320 }; arrayList->insert(-1, &p5, arrayList);
struct Person p6 = { "小龙女", 950 }; arrayList->insert(2, &p6, arrayList);
arrayList->foreach(printArray, arrayList);
printf("删除数据前,容量:%d 长度:%d\n", arrayList->capacity(arrayList), arrayList->size(arrayList));
printf("删除索引%d元素!\n", 5);
struct Person* personIndex = arrayList->removeByIndex(5, arrayList);
arrayList->foreach(printArray, arrayList);
printf("删除数据前,容量:%d 长度:%d\n", arrayList->capacity(arrayList), arrayList->size(arrayList));
printf("删除[姓名:%s--年龄:%d]元素!\n", p6.name, p6.age);
struct Person* personData = arrayList->removeByData(&p6, compare, arrayList);
arrayList->foreach(printArray, arrayList);
printf("清空数据前,容量:%d 长度:%d\n", arrayList->capacity(arrayList), arrayList->size(arrayList));
void** clear = arrayList->clear(arrayList);
printf("清空数据,容量:%d 长度:%d\n", arrayList->capacity(arrayList), arrayList->size(arrayList));
printf("销毁数组!\n");
void** destroy = arrayList->destroy(arrayList);
return 0;
}
运行结果:
初始化数组容量不能小于0,修改初始容量为5!
新建动态数组长度0
插入数据前,容量:5 长度:0
姓名:猪八戒--年龄:800
姓名:白龙马--年龄:502
姓名:小龙女--年龄:950
姓名:孙悟空--年龄:650
姓名:唐三藏--年龄:24
姓名:沙和尚--年龄:320
删除数据前,容量:10 长度:6
删除索引5元素!
姓名:猪八戒--年龄:800
姓名:白龙马--年龄:502
姓名:小龙女--年龄:950
姓名:孙悟空--年龄:650
姓名:唐三藏--年龄:24
删除数据前,容量:10 长度:5
删除[姓名:小龙女--年龄:950]元素!
姓名:猪八戒--年龄:800
姓名:白龙马--年龄:502
姓名:孙悟空--年龄:650
姓名:唐三藏--年龄:24
清空数据前,容量:10 长度:4
清空数据,容量:10 长度:0
销毁数组!
从测试的代码中已经看不到数组的创建与运用过程了,只能看见对动态数组的使用与操作。
2.3.线性表的链式存储结构
线性表顺序存储结构的特点是,在逻辑关系上相邻的两个元素在物理位置上也是相邻的,因此可以随机存取表中任一元素。但是,当经常需要做插入和删除操作运算时,则需要移动大量的元素,而采用链式存储结构时就可以避免这些移动。然而,由于链式存储结构存储线性表数据元素的存储空间可能是连续的,也可能是不连续的,因而链表的结点是不可以随机存取的。链式存储是最常用的存储方式一,不仅可以用来表示线性表,而且还可以用来表示各种非线性的数据结构,链式存储结构又叫链接存储结构。在后面的章节中将反复使用这种存储方式。
2.3.1.单链表(线性链表)
在使用链式存储结构表示每个数据元素ai时,除了存储ai本身的信息之外,还需要一个存储指示其后继元素ai+1存储位置的指针,由这两个部分组成元素ai的存储映象通常称为结点。它包括两个域:存储数据元素的域称为数据域,存储直接后继存储地址的域称为指针域。利用这种存储方式表示的线性表称为链表,链表中一个结点的存储结构为:
n个结点链成一个链表,即为线性表(a1,a2, …,an)的链式存储结构。由于这种链表的每个结点中只包含一个指针域,因此又称为单链表,其抽象表示如图2.2所示。
显然,单链表中每个结点的存储地址是存放在其直接前趋结点的指针域(next)中,而开始结点无直接前趋,因此设立头指针head指向开始结点。又由于终端结点无后继结点,所以终端结点的指针域为空,即NULL(在此及后面的图示中用“Λ”表示)。如果链表中一个结点也没有,则为空链表,这时head=NULL。
由此可见,一个单链表可由头指针唯一确定,因此单链表可以用头指针的名字来命名。用C语言中的结构类型描述线性表的链式存储结构如下:
注意,这里的LinkList和ListNode*是不同名字的同一指针类型,取名的不同是为了在概念上更加明确。特别值得注意的是指针变量和指针指向的变量(结点变量)这两个概念。指针变量的值要么为空(NULL),不指向任何结点;要么为非空,即它的值是一个结点的存储地址。指针变量所指向的结点地址并没有具体说明,而是在程序的执行过程中需要存放结点时才产生,是通过C语言的标准函数malloc()实现的。例如,给指针变量p分配一个结点的地址:p=(ListNode *)malloc(sizeof(ListNode));该语句的功能是申请分配一个类型为ListNode的结点的地址空间,并将其首地址存入指针变量p中。当结点不需要时可以用标准函数free§释放结点存储空间。
链表中的结点变量是通过指针变量来访问的。因为在C语言中是用p->来表示p所指向的变量的,又由于结点类型是一个结构类型,因此可用p->data和p->next分别表示结点的数据域变量和指针域变量。注意,当p为空值时,则不指向任何结点,此时不能通过p来访问结点,否则会引起程序错误。
2.3.2.单链表上的基本运算
下面讨论线性表在单链表存储结构上的几种基本运算。
1. 建立单链表
动态建立单链表的常用方法有两种:一种是头插法,另一种则是尾插法。
(1)头插法建表
头插法建表是从一个空表开始,重复读入数据,生成新结点,将读入的数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头上,直到读入结束标志为止。假设线性表中的结点的数据域为字符型,其具体算法如下:
例如,在空链表head中依次插入数据域分别为a、b、c的结点之后,将x为数据域的新结点p插入到当前链表表头,其指针的修改变化情况如图2.3所示。
(2)尾插法建表
头插法建立链表是将新结点插入在表头,算法比较简单,但新建链表中结点的次序和输入时的顺序相反,理解时不大直观。若需要和输入次序一致,则可使用尾插法建立链表。该方法是将新结点插入在当前链表的表尾上,因此需要增设一个尾指针rear,使其始终指向链表的尾结点。例如,在head中依次插入数据域分别为a、b、c的结点之后,将x为数据域的新结点p插入到当前链表表尾,其指针的修改变化情况如图2.4所示。
假设线性表中结点的数据域为字符型,其具体算法如下:
为了简化算法,方便操作,可在链表的开始结点之前附加一个结点,并称其为头结点。
带头结点的单链表结构图2.5所示。
在引入头节点之后,尾插法建立单链表的算法可简化为:
2. 查找运算(带头结点)
在单链表中,任何两个结点的存储位置之间没有固定的联系,每个结点的存储位置包含在其直接前趋的指针域中。因此,在单链表中存取第i个结点时,必须从表头结点开始搜索,所以链表结构是非随机存取的存储结构。若链表带头结点时,就应特别注意头结点和表头结点(即开始结点)的区别。
(1)按结点序号查找
在单链表中要查找第i个结点,就必须从链表的第1个结点(开始结点,序号为1)开始,序号为0的是头结点, p指向当前结点, j为计数器,其初始值为1,当p扫描下一个结点时,计数器加1。当j=i时,指针p所指向的结点就是要找的结点。其算法如下:
该算法的基本操作是比较j和i以及后移指针。循环中的语句频度与被查结点i在链表中的位置有关,若1≤i≤n,则频度为i-1,否则频度为n。所以,在等概率情况下,平均时间复杂度为:
(2)按结点值查找
在单链表中按值查找结点,就是从链表的开始结点出发,顺链逐个将结点的值和给定值k进行比较,若遇到相等的值,则返回该结点的存储位置,否则返回NULL。按值查找算法要比按序号查找更为简单,其算法如下:
该算法的时间主要花在查找操作上,循环最坏情况下执行n次,因此其算法时间复杂度为O(n)。
3. 插入运算
从前面顺序表的插入运算可知,插入运算是将值为x的新结点插入到表的第i个结点的位置上,即插入到ai-1与ai之间。然而,链表和顺序表的插入运算是不同的,顺序表在插入时要移动大量的结点,而链表在插入时不需要移动结点,但需要用移动指针来进行位置查找(顺序表和链表插入运算区别)。
其算法思想:先使p指向ai-1的位置,然后生成一个数据域值为x的新结点*s,再进行插入操作,其插入过程如图2.6所示。
具体算法如下:
插入算法不需要移动表结点,但为了使p指向第i-1个结点,仍然需要从表头开始进行查找,因此该插入算法的时间复杂度也是O(n)。
4. 删除运算
删除运算就是将链表的第i个结点从表中删去。由于第i个结点的存储地址是存储在第i-1个结点的指针域next中,因此要先使p指向第i-1个结点,然后使得p->next指向第i+1个结点,再将第i个结点释放掉。此操作过程如图2.7所示。
其具体算法如下:
从上述的算法可以看到,删除算法与插入算法的时间复杂度一样,都是O(n)。
5. 单链表上运算举例
【例2.5】试写一个算法,将一个头结点指针为a的带头结点的单链表A分解成两个单链表A和B,其中头结点指针分别为a和b,使得A链表中含有原链表A中序号为奇数的元素,而B链表中含有原链表中序号为偶数的元素,并保持原来的相对顺序。
分析:根据题目要求,需要遍历整个链表A,当遇到序号为奇数的结点时将其链接到A表上,序号为偶数结点时,将其链接到B表中,一直到遍历结束。其实现算法如下:
这个算法要从头至尾扫描整个链表,所以它的时间复杂度是O(n)。
【例2.6】假设头指针为La和Lb的单链表(带头结点)分别为线性表A和B的存储结构,两个链表都是按结点数据值递增有序的。试写一个算法,将这两个单链表合并为一个有序链表Lc。
分析:根据题目要求设立三个指针pa、pb和pc,其中pa和pb分别指向La表和Lb表中当前待比较插入的结点,而pc则指向Lc表当前的最后一个结点,若pa->data≤pb->data,则将pa所指向的结点链接到pc所指的结点之后,否则将pb所指向的结点链接到pc所指的结点之后。其实现算法如下:
该算法要从头至尾扫描两个表,用一个循环,所以他的时间复杂度是O(n)。
6. 单链表封装
C语言不支持函数的重载,同样对链表做小步重构,以便于后面很方便的复用链表。
借鉴Java的封装思想,将链表的创建与运用过程封装起来不对外暴露,将对链表的创建与操作功能对外暴露,为了防止函数名称相同导致无法使用的问题,将对外暴露的操作功能封装到一个结构体当中,类似Java的对象使用方式。
编码采用Visual Studio 2022编译器编写。
1. 重构链表(链表维护元素):
构建头文件linkValueList.h声明函数。
#pragma once
#ifndef LINK_LIST_H
#define LINK_LIST_H
struct LinkValueList
{
struct List* list;
void (*insert)(int position, void* data, struct List* list);
void (*foreach)(void (*printData)(void* data), struct List* list);
void (*removeByIndex)(int position, struct List* list);
void (*removeByData)(void* data, int (*compare)(void* firstData, void* secondData), struct List* list);
int (*size)(struct List* list);
void** (*clear)(struct List* list);
void** (*destroy)(struct List* list);
};
struct LinkValueList* initLinkValueList();
#endif
实现头文件功能linkValueList.c。
#define _CRT_SECURE_NO_WARNINGS //规避C4996告警
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "linkValueList.h"
struct List* initValueList();
void insertLinkValueList(int position, void* data, struct LinkValueList* linkList);
void foreachLinkValueList(void (*printData)(void* data), struct LinkValueList* linkList);
void removeValueListByIndex(int position, struct LinkValueList* linkList);
void removeValueListByData(void* data, int (*compare)(void* firstData, void* secondData), struct LinkValueList* linkList);
int sizeLinkValueList(struct LinkValueList* linkList);
void** clearLinkValueList(struct LinkValueList* linkList);
void** destroyLinkValueList(struct LinkValueList* linkList);
struct LinkValueList* initLinkValueList()
{
struct LinkValueList* linkList = malloc(sizeof(struct LinkValueList));
struct List* list = initValueList();
if (linkList == NULL || list == NULL)
{
printf("初始化链表操作失败,分配链表空间失败!");
return NULL;
}
linkList->list = list;
linkList->insert = insertLinkValueList;
linkList->foreach = foreachLinkValueList;
linkList->removeByIndex = removeValueListByIndex;
linkList->removeByData = removeValueListByData;
linkList->size = sizeLinkValueList;
linkList->clear = clearLinkValueList;
linkList->destroy = destroyLinkValueList;
}
struct LinkNode
{
void* data;
struct LinkNode* next;
};
struct List
{
struct LinkNode header;
int size;
};
struct List* initValueList()
{
struct List* list = malloc(sizeof(struct List));
if (list == NULL)
{
printf("初始化链表失败,分配链表空间失败!");
return NULL;
}
list->header.data = NULL;
list->header.next = NULL;
list->size = 0;
return list;
}
void insertLinkValueList( int position, void* data, struct LinkValueList* linkList)
{
struct List* list = linkList->list;
if (list == NULL)
{
printf("链表为空!");
return;
}
if (position < 0 || position > list->size - 1)
{
position = list->size;
}
// First pointer the header.
struct LinkNode* currentPointer = &list->header;
for (int i = 0; i < position; i++)
{
currentPointer = currentPointer->next;
}
struct LinkNode* currentNode = malloc(sizeof(struct LinkNode));
if (currentNode == NULL)
{
return;
}
currentNode->data = data;
currentNode->next = currentPointer->next;
currentPointer->next = currentNode;
list->size++;
}
void foreachLinkValueList(void (*printData)(void* data), struct LinkValueList* linkList)
{
struct List* list = linkList->list;
if (list == NULL)
{
printf("链表为空!");
return;
}
struct LinkNode* currentPointer = list->header.next;
for (int i = 0; i < list->size; i++)
{
printData(currentPointer->data);
currentPointer = currentPointer->next;
}
}
void removeValueListByIndex(int position, struct LinkValueList* linkList)
{
struct List* list = linkList->list;
if (list == NULL)
{
printf("链表为空!");
return;
}
if (position < 0 || position > list->size - 1)
{
printf("删除链表位置%d不合法!\n", position);
return;
}
// First pointer the header.
struct LinkNode* currentPointer = &list->header;
for (int i = 0; i < position; i++)
{
currentPointer = currentPointer->next;
}
currentPointer->next = currentPointer->next->next;
free(currentPointer->next);
currentPointer->next = NULL;
list->size--;
}
void removeValueListByData(void* data, int (*compare)(void* firstData, void* secondData), struct LinkValueList* linkList)
{
struct List* list = linkList->list;
if (list == NULL)
{
printf("链表为空!");
return;
}
struct LinkNode* currentPointer = &list->header;
struct LinkNode* priorPointer = currentPointer;;
for (int i = 0; i < list->size; i++)
{
currentPointer = currentPointer->next;
if (compare(currentPointer->data, data))
{
break;
}
priorPointer = priorPointer->next;
}
priorPointer->next = currentPointer->next;
free(currentPointer);
currentPointer = NULL;
list->size--;
}
int sizeLinkValueList(struct LinkValueList* linkList)
{
struct List* list = linkList->list;
if (list == NULL)
{
printf("链表为空!");
return;
}
return list->size;
}
void** clearLinkValueList(struct LinkValueList* linkList)
{
struct List* list = linkList->list;
if (list == NULL)
{
printf("链表为空!");
return NULL;
}
void** result = malloc(sizeof(void *) * list->size);
if (result == NULL)
{
printf("清空链表失败,没有足够的临时空间存放链表中的数据!");
return NULL;
}
struct LinkNode* currentPointer = list->header.next;
struct LinkNode* nextPointer = NULL;
if (currentPointer != NULL)
{
nextPointer = currentPointer->next;
}
for (int i = 0; i < list->size; i++)
{
if (currentPointer != NULL)
{
result[i] = currentPointer->data;
free(currentPointer);
currentPointer = nextPointer;
if (nextPointer != NULL && nextPointer->next != NULL)
{
nextPointer = nextPointer->next;
}
else if (i == list->size - 1)
{
currentPointer = NULL;
nextPointer = NULL;
}
}
}
list->header.next = NULL;
list->size = 0;
return result;
}
void** destroyLinkValueList(struct LinkValueList* linkList)
{
struct List* list = linkList->list;
if (list == NULL)
{
printf("链表为空!");
return;
}
void** result = clearLinkValueList(linkList);
free(list);
list = NULL;
free(linkList);
linkList = NULL;
return result;
}
测试:
#define _CRT_SECURE_NO_WARNINGS //规避C4996告警
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "linkValueList.h"
struct Person
{
void* node;
char name[64];
int age;
};
void printList(void* data)
{
struct Person* person = data;
printf("姓名:%s--年龄:%d\n", person->name, person->age);
}
int compare(void* firstData, void* secondData)
{
struct Person* firstPerson = firstData;
struct Person* secondPerson = secondData;
return strcmp(firstPerson->name, secondPerson->name) == 0 && firstPerson->age == secondPerson->age;
}
int main()
{
struct LinkValueList* linkList = initLinkValueList();
struct Person p1 = { NULL, "唐三藏", 24 }; linkList->insert(0, &p1, linkList);
struct Person p2 = { NULL, "白龙马", 502 }; linkList->insert(0, &p2, linkList);
struct Person p3 = { NULL, "孙悟空", 650 }; linkList->insert(1, &p3, linkList);
struct Person p4 = { NULL, "猪八戒", 800 }; linkList->insert(0, &p4, linkList);
struct Person p5 = { NULL, "沙和尚", 320 }; linkList->insert(-1, &p5, linkList);
struct Person p6 = { NULL, "小龙女", 950 }; linkList->insert(2, &p6, linkList);
printf("插入链表:\n");
linkList->foreach(printList, linkList);
printf("按索引删除链表数据:\n");
linkList->removeByIndex(5, linkList);
linkList->foreach(printList, linkList);
printf("按数值删除链表数据:\n");
linkList->removeByData(&p6, compare, linkList);
linkList->foreach(printList, linkList);
printf("链表长度:%d, 清空链表:\n", linkList->size(linkList));
void** clear = linkList->clear(linkList);
linkList->foreach(printList, linkList);
printf("链表长度:%d, 链表被清空。\n", linkList->size(linkList));
printf("链表长度:%d, 销毁链表:\n", linkList->size(linkList));
void** destroy = linkList->destroy(linkList);
printf("销毁链表。\n");
return 0;
}
运行结果:
插入链表:
姓名:猪八戒--年龄:800
姓名:白龙马--年龄:502
姓名:小龙女--年龄:950
姓名:孙悟空--年龄:650
姓名:唐三藏--年龄:24
姓名:沙和尚--年龄:320
按索引删除链表数据:
姓名:猪八戒--年龄:800
姓名:白龙马--年龄:502
姓名:小龙女--年龄:950
姓名:孙悟空--年龄:650
姓名:唐三藏--年龄:24
按数值删除链表数据:
姓名:猪八戒--年龄:800
姓名:白龙马--年龄:502
姓名:孙悟空--年龄:650
姓名:唐三藏--年龄:24
链表长度:4, 清空链表:
链表长度:0, 链表被清空。
链表长度:0, 销毁链表:
销毁链表。
2. 重构企业级链表(链表只维护指针):
构建头文件linkList.h声明函数。
#pragma once
#ifndef LINK_LIST_H
#define LINK_LIST_H
struct LinkNode
{
struct LinkNode* next;
};
struct List
{
struct LinkNode header;
struct LinkNode tail;
int size;
};
struct LinkList
{
struct List* list;
void (*insert)(int position, void* data, struct LinkList* linkList);
int (*addSort)(void* data, int (*sort)(void* data, void* linkListNode), struct LinkList* linkList);
int (*joint)(struct LinkList* sourceLink, int start, int size, struct LinkList* linkList);
void (*foreach)(void (*printData)(void* data), struct LinkList* linkList);
void (*removeByIndex)(int position, struct LinkList* linkList);
void (*removeByData)(void* data, int (*compare)(void* firstData, void* secondData), struct LinkList* linkList);
int (*size)(struct LinkList* linkList);
void** (*clear)(struct LinkList* linkList);
void** (*destroy)(struct LinkList* linkList);
};
struct LinkList* initLinkList();
#endif
实现头文件功能linkList.c。
#define _CRT_SECURE_NO_WARNINGS //规避C4996告警
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "linkList.h"
struct List* initList();
void insertLinkList(int position, void* data, struct LinkList* linkList);
int addSortLinkList(void* data, int (*sort)(void* data, void* linkListNode), struct LinkList* linkList);
int jointLinkList(struct LinkList* sourceLink, int start, int size, struct LinkList* linkList);
void foreachLinkList(void (*printData)(void* data), struct LinkList* linkList);
void removeListByIndex(int position, struct LinkList* linkList);
void removeListByData(void* data, int (*compare)(void* firstData, void* secondData), struct LinkList* linkList);
int sizeLinkList(struct LinkList* linkList);
void** clearLinkList(struct LinkList* linkList);
void** destroyLinkList(struct LinkList* linkList);
struct LinkList* initLinkList()
{
struct LinkList* linkList = malloc(sizeof(struct LinkList));
struct List* list = initList();
if (linkList == NULL || list == NULL)
{
printf("初始化链表操作,分配内存失败!");
return NULL;
}
linkList->list = list;
linkList->insert = insertLinkList;
linkList->addSort = addSortLinkList;
linkList->joint = jointLinkList;
linkList->foreach = foreachLinkList;
linkList->removeByIndex = removeListByIndex;
linkList->removeByData = removeListByData;
linkList->size = sizeLinkList;
linkList->clear = clearLinkList;
linkList->destroy = destroyLinkList;
}
struct List* initList()
{
struct List* list = malloc(sizeof(struct List));
if (list == NULL)
{
printf("初始化链表,分配内存失败!");
return NULL;
}
list->header.next = NULL;
list->tail.next = &list->header;
list->size = 0;
return list;
}
void insertLinkList(int position, void* data, struct LinkList* linkList)
{
struct List* list = linkList->list;
if (list == NULL)
{
printf("链表为空!");
return;
}
if (position < 0 || position > list->size)
{
position = list->size;
}
if (data == NULL)
{
return;
}
struct LinkNode* insertData = data;
struct LinkNode* currentPointer = &list->header;
for (int i = 0; i < position; i++)
{
currentPointer = currentPointer->next;
}
if (currentPointer->next == NULL)
{
list->tail.next = insertData;
}
insertData->next = currentPointer->next;
currentPointer->next = insertData;
list->size++;
}
int addSortLinkList(void* data, int (*sort)(void* data, void* linkListNode), struct LinkList* linkList)
{
if (linkList == NULL || linkList->list == NULL)
{
printf("链表为空!");
return -1;
}
struct List* list = linkList->list;
struct LinkNode* currentPointer = list->header.next;
if (currentPointer == NULL)
{
insertLinkList(0, data, linkList);
return 1;
}
int position = -1;
while (currentPointer != NULL)
{
position++;
if (sort(data, currentPointer))
{
insertLinkList(position, data, linkList);
return 1;
}
if(currentPointer->next == NULL)
{
insertLinkList(list->size, data, linkList);
return 1;
}
currentPointer = currentPointer->next;
}
return 0;
}
int jointLinkList(struct LinkList* sourceLink, int start, int size, struct LinkList* linkList)
{
if (sourceLink == NULL || sourceLink->list == NULL ||
linkList == NULL || linkList->list == NULL
)
{
printf("链表为空!");
return -1;
}
struct LinkNode* currentPointer = &sourceLink->list->header;
for (int i = 0; i < start; i++)
{
currentPointer = currentPointer->next;
}
struct List* list = linkList->list;
list->tail.next->next = currentPointer->next;
for (int i = 0; i < size; i++)
{
currentPointer = currentPointer->next;
}
list->tail.next = currentPointer;
list->size += sourceLink->list->size;
return 1;
}
void foreachLinkList(void (*printData)(void* data), struct LinkList* linkList)
{
struct List* list = linkList->list;
if (list == NULL)
{
printf("链表为空!");
return;
}
struct LinkNode* currentPointer = &list->header;
for (int i = 0; i < list->size; i++)
{
currentPointer = currentPointer->next;
printData(currentPointer);
}
}
void removeListByIndex(int position, struct LinkList* linkList)
{
struct List* list = linkList->list;
if (list == NULL)
{
printf("链表为空!");
return;
}
if (position < 0 || position > list->size - 1)
{
printf("删除链表位置%d不合法!\n", position);
return;
}
// First pointer the header.
struct LinkNode* currentPointer = &list->header;
for (int i = 0; i < position; i++)
{
currentPointer = currentPointer->next;
}
currentPointer->next = currentPointer->next->next;
free(currentPointer->next);
currentPointer->next = NULL;
list->size--;
}
void removeListByData(void* data, int (*compare)(void* firstData, void* secondData), struct LinkList* linkList)
{
struct List* list = linkList->list;
if (list == NULL)
{
printf("链表为空!");
return;
}
struct LinkNode* currentPointer = &list->header;
struct LinkNode* priorPointer = currentPointer;;
for (int i = 0; i < list->size; i++)
{
currentPointer = currentPointer->next;
if (compare(currentPointer, data))
{
break;
}
priorPointer = priorPointer->next;
}
priorPointer->next = currentPointer->next;
list->size--;
}
int sizeLinkList(struct LinkList* linkList)
{
struct List* list = linkList->list;
if (list == NULL)
{
printf("链表为空!");
return;
}
return list->size;
}
void** clearLinkList(struct LinkList* linkList)
{
struct List* list = linkList->list;
if (list == NULL)
{
printf("链表为空!");
return NULL;
}
void** result = malloc(sizeof(void*) * list->size);
if (result == NULL)
{
printf("清空链表失败,没有足够的临时空间存放链表中的数据!");
return NULL;
}
struct LinkNode* currentPointer = &list->header;
for (int i = 0; i < list->size; i++)
{
currentPointer = currentPointer->next;
result[i] = currentPointer;
}
list->header.next = NULL;
list->tail.next = &list->header;
list->size = 0;
return result;
}
void** destroyLinkList(struct LinkList* linkList)
{
struct List* list = linkList->list;
if (list == NULL)
{
printf("链表为空!");
return;
}
void** result = clearLinkList(linkList);
free(list);
list = NULL;
free(linkList);
linkList = NULL;
return result;
}
测试
#define _CRT_SECURE_NO_WARNINGS //规避C4996告警
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "linkList.h"
struct Person
{
void* node;
char name[64];
int age;
};
void printList(void* data)
{
struct Person* person = data;
printf("姓名:%s--年龄:%d\n", person->name, person->age);
}
int compare(void* firstData, void* secondData)
{
struct Person* firstPerson = firstData;
struct Person* secondPerson = secondData;
return strcmp(firstPerson->name, secondPerson->name) == 0 && firstPerson->age == secondPerson->age;
}
int main()
{
struct LinkList* linkList = initLinkList();
struct Person p1 = { NULL, "唐三藏", 24 }; linkList->insert(0, &p1, linkList);
struct Person p2 = { NULL, "白龙马", 502 }; linkList->insert(0, &p2, linkList);
struct Person p3 = { NULL, "孙悟空", 650 }; linkList->insert(1, &p3, linkList);
struct Person p4 = { NULL, "猪八戒", 800 }; linkList->insert(0, &p4, linkList);
struct Person p5 = { NULL, "沙和尚", 320 }; linkList->insert(-1, &p5, linkList);
struct Person p6 = { NULL, "小龙女", 950 }; linkList->insert(2, &p6, linkList);
printf("插入链表:\n");
linkList->foreach(printList, linkList);
printf("按索引删除链表数据:\n");
linkList->removeByIndex(5, linkList);
linkList->foreach(printList, linkList);
printf("按数值删除链表数据:\n");
linkList->removeByData(&p6, compare, linkList);
linkList->foreach(printList, linkList);
printf("链表长度:%d, 清空链表:\n", linkList->size(linkList));
void** clear = linkList->clear(linkList);
linkList->foreach(printList, linkList);
printf("链表长度:%d, 链表被清空。\n", linkList->size(linkList));
printf("链表长度:%d, 销毁链表:\n", linkList->size(linkList));
void** destroy = linkList->destroy(linkList);
printf("销毁链表。\n");
return 0;
}
运行结果
插入链表:
姓名:猪八戒--年龄:800
姓名:白龙马--年龄:502
姓名:小龙女--年龄:950
姓名:孙悟空--年龄:650
姓名:唐三藏--年龄:24
姓名:沙和尚--年龄:320
按索引删除链表数据:
姓名:猪八戒--年龄:800
姓名:白龙马--年龄:502
姓名:小龙女--年龄:950
姓名:孙悟空--年龄:650
姓名:唐三藏--年龄:24
按数值删除链表数据:
姓名:猪八戒--年龄:800
姓名:白龙马--年龄:502
姓名:孙悟空--年龄:650
姓名:唐三藏--年龄:24
链表长度:4, 清空链表:
链表长度:0, 链表被清空。
链表长度:0, 销毁链表:
销毁链表。
通过上面两种不同的链表结构可以观察到,在测试代码中仅仅只有初始化链表部分的代码不同,struct LinkList* linkList = initLinkList()与struct LinkValueList* linkList = initLinkValueList()两行代码,其它部分测试代码完全相同,测试结果也完全相同。
从以上代码的重构中可以看到重构为编写代码带来的便利。
2.3.3.循环链表
循环链表是链式存储结构的另一种形式。其特点是单链表中最后一个结点(终端结点)的指针域不为空,而是指向链表的头结点,使整个链表构成一个环。因此,从表中任一结点开始都可以访问表中其他结点。这种结构形式的链表称为单循环链表。类似地,还可以有多重链的循环链表。
在单循环链表中,为了使空表和非空表的处理一致,需要设置头结点。非空的单循环链表如图2.8所示。
循环链表的结点类型与单链表完全相同,在操作上也与单链表基本一致,差别仅在于算法中循环的结束判断条件不再是p或p->next是否为空,而是它们是否等于头指针。
在用头指针表示的单循环链表中,查找任何结点都必须从开始结点查起,而在实际应用中,表的操作常常会在表尾进行,此时若用尾指针表示单循环链表,可使某些操作简化。仅设尾指针的单循环链表如图2.9所示。
【例2.7】已知有一个结点数据域为整型的,且按从大到小顺序排列的头结点指针为L的非空单循环链表,试写一算法插入一个结点(其数据域为x)至循环链表的适当位置,使之保持链表的有序性。
分析:要解决这个算法问题,首先就是要解决查找插入位置问题,查找就要使用循环语句,从表头开始逐个结点比较。判断整个循环链表是否比较结束,就是判断当前结点的指针域是否等于头指针。另外,由于链表是递减有序的,在查找插入位置时,循环条件是当前结点值是否大于要插入的结点值,所以,若用q指向表中当前结点,那么循环条件应该是q->data>x &&q!=L,还要使用一个指针p指向插入位置的前趋结点。实现本题功能的算法如下:
该算法在最好情况下,也就是插入在第一个结点前面,循环一次也不执行;在最坏情况下,即插在最后一个结点之后,当循环需要执行n次,因此,该算法的时间复杂度为O(n)。
2.3.4.双向链表
以上讨论的单链表和单循环链表的结点中只设有一个指向其直接后继的指针域,因此,从某个结点出发只能顺指针向后访问其他结点。若需要查找结点的直接前趋,则需要从头指针开始查找某结点的直接前趋结点。若希望从表中快速确定一个结点的直接前趋,只要在单链表的结点类型中增加一个指向其直接前趋的指针域prior即可。这样形成的链表中有两条不同方向的链,因此称之为双向链表。双向链表及其结点类型描述如下:
同单链表类似,双向链表一般也由头指针head唯一确定。为了某些操作运算的方便,双链表也增设一个头结点,若将尾结点和头结点链接起来也就构成了循环链表。因此,我们所讨论的往往是这种双向循环链表,其结点结构、空双向链表和非空双向链表如图2.10所示。
由例2.7可知,在单链表的给定结点前插入一个新结点,必须使用一指针指向给定结点的直接前趋,由于单链表是单向的,因此需要从表头开始向后搜索链表才能找到已知结点的直接前趋。同理,在单链表上删除给定结点的运算也同样存在这样的问题。而双向链表既有向前链接的链,也有向后链接的链,所以在做链表上结点的插入和删除操作时就显得十分方便。
例如,在双向链表的给定结点前插入一结点的操作过程如图2.11所示。设p为给定结点的指针, x为待插入结点的值,其实现算法如下:
因为不再需要查找指向删除结点的前趋结点的指针,所以在双向链表上删除指定结点*p的算法更为简单,其删除操作过程如图2.12所示,其删除算法如下:
注意:与单链表的插入和删除操作不同的是,在双向链表中进行结点插入和删除时,必须同时修改两个方向上的指针。
【例2.8】 假设有一个头结点指针为head的循环双向链表,其结点类型结构包括三个域: prior, data和next。其中data为数据域, next为指针域,指向其后继结点, prior也为指针域,其值为空(NULL),因此该双向链表其实是一个单循环链表。试写一算法,将其表修改为真正的双向循环链表。
分析:根据题目的要求,就是要将表中的每个prior域填上相应的前趋结点地址。只要弄清楚双向循环链表的结构,其算法就非常简单。其实只需要以p指向头结点开始,循环使用语句p->next->prior->p;和p=p->next;即可实现题目要求。具体实现算法如下:
2.4.顺序表和链表的比较
线性表有两种存储结构:顺序存储结构(顺序表)和链式存储结构(链表)。这两种存储表示各有其特点:顺序表结构可以随机存取表中任一元素,元素的存储位置可用一个简单的公式来表示,然而在做插入和删除操作时,需要移动大量元素。而链式存储结构则可克服在做插入和删除操作时移动大量元素的问题,但却失去了随机访问的特点。那么在实际应用中,究竟选择哪种存储结构呢?这就要根据具体问题的要求和性质来决定。通常从两方面性能来!
1. 时间性能
如果在实际问题中,对线性表的操作是经常性的查找运算,以顺序表形式存储为宜。因为顺序存储是一种随机存取结构,可以随机访问任一结点,访问每个结点的时间代价是一样的,即每个结点的存取时间复杂度均为O(1)。而链式存储结构必须从表头开始沿链逐一访问各结点,其时间复杂度为O(n)。
如果经常进行的运算是插入和删除运算,以链式存储结构为宜。因为顺序表作插入和删除操作需要移动大量结点,而链式结构只需要修改相应的指针。
2. 空间性能
顺序表的存储空间是静态分配的,在应用程序执行之前必须给定空间大小。若线性表的长度变化较大,则其存储空间很难预先确定,设置过大将产生空间浪费,设定过小会使空间溢出,因此对数据量大小能事先知道的应用问题,适合使用顺序存储结构。而链式存储是动态分配存储空间,只要内存有空闲空间,就不会产生溢出,因此对数据量变化较大的动态问题,以链式存储结构为好。
对于线性表结点的存储密度问题,也是选择存储结构的一个重要依据。所谓存储密度就是结点空间的利用率。它的计算公式为:
存储密度 =(结点数据域所占空间)/(整个结点所占空间)
一般来说,结点存储密度越大,存储空间的利用率就越高。显然,顺序表结点的存储密度是1,而链表结点的存储密度肯定小于1。例如:若单链表结点数据域为整型数,指针所占的存储空间和整型数相同,则其结点的存储密度为50%。因此,若不考虑顺序表的空闲区,则顺序表的存储空间利用率为100%,远高于单链表的结点存储密度。
小 结
本章着重介绍了线性表在顺序存储结构和链式存储结构上各种运算实现的算法,并给出了算法的时间复杂度的分析。特别是链式存储,它是一种动态管理的数据结构,可以灵活地进行元素的插入和删除。顺序表和链表是计算机中最基本的数据结构,是后续章节中许多复杂数据结构的实现基础,应熟练掌握和灵活应用。因此,本章内容是全书的学习重点,通过对本章的学习,特别是通过对线性表的顺序存储和链式存储结构及在其基础上的基本运算算法的理解,并通过实例,提高读者设计算法解决实际应用问题的能力。