1.抽象数据类型
抽象数据类型是指一系列操作的集合。抽象数据类型是数学上的抽象,在ADT的定义中并没有指出这些操作的具体实现,只给出了操作的功能。当需要使用ADT时,由程序员根据所需要的操作来进行编写,需要进行ADT中的操作时只需要调用已经写好的函数即可。
2.表ADT
一般处理的是形如 A1,A2...An 的表,这个表的大小为n,大小为0的表称为空表。对除空表外的表,称 Ai+1 为 Ai 的后继,称 Ai-1 为 Ai 的前驱,不定义 A1 的前驱,也不定义 An 的后继。表ADT中有:PrintList(打印表中元素)、Insert(向表中插入元素)、Delete(删除表中元素)、Find(找到表中对应元素)、FindKth(找到表中对应位置处的元素)、MakeEmpty(将表置空)等。这些操作可增可删,并且其具体实现应该由程序员自己决定。
3.表的简单数组实现
#include <stdio.h>
#include <stdlib.h>
typedef struct arrList {
int arr[100];//这个数组也可以使用动态内存的形式
int num;
}al;//定义结构体,包含一个数组和记录表中元素个数的变量num
al* CreatListHead() {
al* p = (al*)malloc(sizeof(al));
p->num = 0;
return p;
}//创建一个结构体,并对其初始化
int IsEmpty(al* L) {
if (L->num == 0)
return 1;
return 0;
}//判断表是否为空,时间复杂度为常数
void InsertAhead(al* L, int i) {
al* p = L;
for (int j = L->num; j > 0; j--) {
p->arr[j] = p->arr[j - 1];
}
p->arr[0] = i;
p->num++;
}//在表头部插入一个元素,时间复杂度为O(N)
void PrintList(al* L) {
for (int i = 0; i < L->num; i++) {
printf("%d ", L->arr[i]);
}
printf("\n----------------------------\n");
}//打印表中的元素,时间复杂度为O(N)
void InsertBack(al* p, int i) {
p->arr[p->num] = i;
p->num++;
}//在表尾部插入一个元素,时间复杂度为常数
void InsertMid(al* p, int pos, int x) {
for (int i = p->num; i > pos; i--) {
p->arr[i] = p->arr[i - 1];
}
p->arr[pos] = x;
p->num++;
}//在表中间插入一个元素,最坏时间复杂度为O(N)
void DeleteList(al* p, int x) {
for (int i = 0; i < p->num; i++) {
if (p->arr[i] == x) {
for (int j = i; j < p->num - 1; j++) {
p->arr[j] = p->arr[j + 1];
}
}
}
p->num--;
}//删除表中的一个元素,时间复杂度为O(N)
void MakeEmpty(al* p) {
free(p);
}//释放表的内存
int main() {
al* List = CreatListHead();
if (IsEmpty(List)) {
printf("The List is empty!\n");
printf("-------------------\n");
}
//从前面插入元素
for (int i = 9; i >= 5; i--) {
InsertAhead(List, i);
}
PrintList(List);
//从后面插入元素
for (int i = 4; i >= 0; i--) {
InsertBack(List, i);
}
PrintList(List);
//从中间插入元素
InsertMid(List, 5, 99);
PrintList(List);
//删除一个元素
DeleteList(List, 7);
PrintList(List);
//查找某个位置的元素直接利用下标返回就行
//删除表
MakeEmpty(List);
return 0;
}
Find所花费的最坏的时间复杂度为O(N),最好的结果是要查找的元素在第一个,所以平均可能花费O(N/2)的时间,而按位置查找则花费常数时间。如果要用插入的方式来建立一个表,时间复杂度为O(N^2)(从头部插入,尾部应该为O(N))。由于数组实现的表,插入和删除所花费的时间过长,所以一般的表(没有限制)一般不使用数组。
4.表的链表实现
由于数组中的元素是在内存中连续排列的,所以使用数组实现表时,不可避免地要对其中的元素进行移动,这就很明显的增加了不必要的开销。如果使用链表来实现,表中的元素在内存中并不是按照顺序存储的,那么对表进行插入和删除时,就不必再移动表中的元素,而只需要改变指针指向的地址即可。
但使用链表实现表ADT时,如果出现以下情况:
①在表的头部插入一个元素时
②删除表头的元素时
③在一般删除时,需要记住被删除元素之前的元素
①、②都有可能因为疏忽而丢失了表的位置,从而造成错误。
使用表头能够很好的解决上面的问题,添加一个表头(哑节点)来记住表的位置,就可以很大程度上避免一些错误。(表头的地址是不会改变的)
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct Lnode {
int value;
struct Lnode* next;
}Lnode;
typedef struct List {
Lnode* head;
int numofele;
}List;
List* CreatList() {
List* ret = (List*)malloc(sizeof(List));
if (ret == NULL) {
perror("malloc:");
return NULL;
}
ret->head = (Lnode*)malloc(sizeof(Lnode));
if (ret->head == NULL) {
perror("malloc:");
return NULL;
}
ret->head->next = NULL;
ret->numofele = 0;
return ret;
}
void DestroyList(List* l) {
if (l == NULL) {
printf("表不存在\n");
return;
}
Lnode* tmp = l->head;
while (tmp) {
tmp = tmp->next;
free(l->head);
l->head = tmp;
}
free(l);
l = NULL;
}
List* ClearList(List* l) {
if (l == NULL) {
printf("表不存在\n");
return NULL;
}
l->numofele = 0;
Lnode* tmp = l->head->next;
Lnode* tmpp = l->head->next;
while (tmp) {
tmp = tmp->next;
free(tmpp);
tmpp = tmp;
}
l->head->next = NULL;
return l;
}
bool IsEmpty(List* l) {
if (l == NULL) {
printf("表不存在\n");
return false;
}
return l->head->next == NULL;
}
int GetListLength(List* l) {
if (l == NULL) {
printf("表不存在\n");
return -1;
}
return l->numofele;
}
int GetElem(List* l, int pos) {//获得表中第pos个元素
if (l == NULL) {
printf("表不存在\n");
return -1;
}
if (IsEmpty(l)) {
printf("表为空\n");
return -1;
}
if (pos < 1) {
printf("输入位置不合法\n");
return -1;
}
else {
Lnode* tmp = l->head;
while (tmp && pos--) {
tmp = tmp->next;
}
if (tmp == NULL) {
printf("不存在第 %d 个元素\n", pos);
return -1;
}
else {
return tmp->value;
}
}
}
Lnode* GetElemPos(List* l, int k) {
if (l == NULL) {
printf("表不存在\n");
return NULL;
}
if (IsEmpty(l)) {
printf("表为空\n");
return NULL;
}
Lnode* tmp = l->head->next;
while (tmp) {
if (tmp->value == k)
return tmp;
tmp = tmp->next;
}
printf("不存在元素 %d\n", k);
return NULL;
}
Lnode* GetPre(List* l, int k) {
if (l == NULL) {
printf("表不存在\n");
return NULL;
}
Lnode* tmp = l->head;
while (tmp->next) {
if (tmp->next->value == k)
return tmp;
tmp = tmp->next;
}
printf("元素 %d 不存在,所以它在表中没有前驱\n", k);
return NULL;
}
//在指定结点处后插
void BackInsert(List* l, Lnode* object, int k) {
Lnode* tmp = (Lnode*)malloc(sizeof(Lnode));
if (tmp == NULL) {
perror("malloc");
return;
}
tmp->value = k;
tmp->next = object->next;
object->next = tmp;
}
//在指定结点前插
void PreInsert(List* l, Lnode* object, int k) {
Lnode* tmp = (Lnode*)malloc(sizeof(Lnode));
if (tmp == NULL) {
perror("malloc");
return;
}
tmp->next = object->next;
object->next = tmp;
tmp->value = object->value;
object->value = k;
}
//在指定位置的插入
void Insert(List* l, int k, int pos) {
if (l == NULL) {
printf("表不存在\n");
return;
}
if (pos < 1 || pos > l->numofele + 1) {
printf("插入位置不合法\n");
return;
}
else {
Lnode* tmp = l->head;
int count = pos - 1;
while (count--) {
tmp = tmp->next;
}
Lnode* p = (Lnode*)malloc(sizeof(Lnode));
if (p == NULL) {
perror("malloc:");
return;
}
p->value = k;
p->next = tmp->next;
tmp->next = p;
l->numofele++;
}
}
//删除指定结点
int ObjectDelete(List* l, Lnode* object) {
int ret = object->value;
if (object->next == NULL) {
Lnode* tmp = l->head->next;
while (tmp && tmp->next != object)
tmp = tmp->next;
if (tmp) {
tmp->next = NULL;
free(object);
object = NULL;
}
else {
printf("该节点不存在\n");
return -1;
}
}
else {
Lnode* tmp = object->next;
object->next = tmp->next;
object->value = tmp->value;
free(tmp);
tmp = NULL;
}
return ret;
}
//删除某个位置的结点
int Delete(List* l, int i) {
if (l == NULL) {
printf("表不存在\n");
return -1;
}
if (IsEmpty(l)) {
printf("表为空,无法删除\n");
return -1;
}
if (i<1 || i>l->numofele) {
printf("该位置不合法\n");
return -1;
}
Lnode* tmp = l->head;
i--;
while (i--) {
tmp = tmp->next;
}
Lnode* de = tmp->next;
int ret = de->value;
tmp->next = de->next;
free(de);
de = NULL;
return ret;
}
void Print(List* l) {
if (l == NULL) {
printf("表不存在\n");
return;
}
if (IsEmpty(l)) {
printf("表为空\n");
return;
}
Lnode* tmp = l->head->next;
while (tmp) {
printf("%d ", tmp->value);
tmp = tmp->next;
}
printf("\n");
}
int main() {
List* l = CreatList();
for (int i = 0; i <= 10; i++) {
Insert(l, i, i + 1);
}
Print(l);
Insert(l, 123, 6);
Print(l);
Delete(l, 12);
Print(l);
return 0;
}
5.数组和链表的对比
①数组需要在使用前指定大小,而链表可以动态开辟空间
②数组的元素是在内存中连续存储的,而链表不是
③插入和删除链表的时间复杂度都小于数组
④数组FindKth的时间复杂度为O(1),而链表最坏为O(N)
⑤对于已知的相同数量的元素,链表所占用的空间要大于数组,因为链表中的每个结构体打都需要多存储一个指针,用来指向下一个元素