一、概述
1.线性表相关概念
(1)第一个元素成为首元,最后一个元素成为尾元。
(2)有序线性表按元素的值递增排列;无序线性表元素的值和位置之间没有直接关系。若不特别说明,默认是无序线性表。
二、顺序表
1.相关概念:
(1)线性表的储存方式有两种,顺序储存方式和链表储存方式。用顺序储存方式实现的线性表成为顺序表,用一维数组作为其储存结构。
(2)特点:
- 在顺序表中,各个元素的逻辑顺序与其存放的物理顺序一致。
- 对于顺序表中的元素,可以进行顺序访问,也可以进行随机访问。
- 顺序表可以用C的一维数组来实现。一维数组又称向量,它是可以静态分配的,也是可以动态分配的。
- 顺序表中的第i个元素被储存在数组下标为 i - 1 的位置上。Loc(i) = Loc(1) + (i - 1) * sizeof(DataType)。其中,Loc(1) 是首元的储存地址。
2.静态储存
(1)特点:
- 储存线性表的一维数组的大小 maxSize 和空间 data 在结构声明中明确指定。
- 编译时,数组空间由编译器固定分配,程序退出后次空间自动释放。
(2)表示:
注:宏定义后面千万别加分号,不然怎么错的你都看不出来!
#include<cstdio>
#include<cstdlib> //不用管为什么引入这个头文件。此文件用不到,在其他文件中可能会用到的。
#define maxSize 100
typedef int DataType;
typedef struct {
DataType data[maxSize];
int n;
}SeqList;
(3)数组元素的数据类型未直接使用int,而是使用了事先定义的DataType。在其他场合需要替换数据类型的话,只需替换这一处即可。
3.动态储存
(1)特点:
- 通过动态储存分配的语句 malloc 动态分配的,一旦数据空间占满,可以另外再分配一块更大的储存空间。
- 表示数组大小的 maxSize 放在顺序表的结构内定义,可以动态地记录扩充后数组空间的大小,进一步提高了结构的灵活性。
(2)函数名: exit()
- 所在头文件:stdlib.h(如果是”VC6.0“的话头文件为:windows.h)
- 功 能: 关闭所有文件,终止正在执行的进程。
- exit(0)表示正常退出,
- exit(x)(x不为0)都表示异常退出,这个x是返回给操作系统(包括UNIX,Linux,和MS DOS)的,以供其他程序使用。
- stdlib.h: void exit(int status); //参 数status,程序退出的返回值
//SeqList.h
#pragma once
#include<cstdio>
#include<cstdlib>
#define initSize 30
typedef int DataType;
typedef struct {
DataType* data;
int maxSize, n;
}SeqList;
void Merge(SeqList& LA, SeqList& LB);
void Intersection(SeqList& LA, SeqList& LB);
//SeqList.cpp
#include "SeqList.h"
void initList(SeqList & L) {
L.data = (DataType*)malloc(initSize * sizeof(DataType));
if (!L.data) { //空指针
printf("内存分配错误!\n"); exit(1);
}
L.maxSize = initSize;
L.n = 0;
}
void clearList(SeqList& L) {
L.n = 0;
}
int Length(SeqList& L) {
return L.n;
}
bool isEmpty(SeqList& L) {
return L.n == 0;
}
bool isFull(SeqList& L) {
return L.n == L.maxSize;
}
bool createList(SeqList& L, DataType A[], int n) {
//从数组A[n]创建顺序表。
L.data = (DataType*)malloc(initSize * sizeof(DataType));
if (!L.data || n > initSize) return false;
for (int i = 0; i < n; i++) L.data[i] = A[i];
L.maxSize = initSize; L.n = n;
return true;
}
int Search(SeqList& L, DataType x) {
for (int i = 0; i < L.n; i++) {
if (L.data[i] == x) return i;
}
return -1;
}
int Locate(SeqList& L, int i) {
//返回第i个元素的物理位置
if (i > 0 && i <= L.n) return i - 1;
else return -1;
}
bool Insert(SeqList& L, int i, DataType& x) {
if (L.n == L.maxSize) return false;
if (i <= 0 || i > L.n + 1) return false;
for (int j = L.n - 1; j >= i - 1; j--) L.data[j + 1] = L.data[j];
L.data[i - 1] = x; L.n++;
return true;
}
bool Remove(SeqList& L, int i, DataType& x) {
//引用参数x的目的是可以保存下来删除的元素的值。
if (!L.n) return false;
if (i <= 0 || i > L.n) return false;
x = L.data[i - 1];
for (int j = i; j < L.n; j++) L.data[j - 1] = L.data[j];
L.n--;
return true;
}
void printList(SeqList& L) {
for (int i = 0; i < L.n - 1; i++) printf("%d ", L.data[i]);
printf("%d\n", L.data[L.n - 1]);
}
//Merge and Intersection.cpp
#include "SeqList.cpp"
void Merge(SeqList& LA, SeqList& LB) {
DataType x;
for (int i = 0; i < Length(LB); i++) {
x = LB.data[i];
int k = Search(LA, x);
if (k == -1) Insert(LA, Length(LA), x);
}
}
void Intersection(SeqList& LA, SeqList& LB) {
DataType x;
int i = 1;
while (i <= Length(LA)){
x = LA.data[i];
int k = Search(LB, x);
if (k == -1) Remove(LA, i, x);
else i++;
}
}
三、单链表
1.相关概念与特点
(1)定义:
- 单链表每一节点包含两个部分,data 和 link。
- NULL 在C中被定义为数值0。
(2)特点:
- 单链表中的数据元素的逻辑顺序与其物理顺序可能不一致,一般通过单链表的指针将各个数据元素按照线性表的逻辑顺序链接起来。
- 单链表只能跟随链接指针逐个结点访问,不能如同顺序表那样直接访问某个固定的结点。
- 由于链接表中的每个结点带有指针域,因而比顺序表需要的存储空间多。
(3)链表的结构定义:
- 具有递归性:结构定义是一个递归的定义,结点定义使用了一个指针,而该指针定义又用到结点。
- 具有隐蔽性:对于一个结点,如果失去了指向它的指针,将无法再找到它。
2.实现
(1)注意:
- 在把元素插入到链表中间的时候一定注意修改指针的顺序。删除同理。
(2)不带头结点的链表:
//linklist.cpp
#include "LinkList.h"
bool Insert(LinkList& first, int i, DataType x) {
if (first == NULL || i == 1) {
LinkNode* newNode = (LinkNode*)malloc(sizeof(LinkNode));
newNode->data = x;
newNode->link = first; first = newNode;
}
else {
LinkNode* p = first, * pr;
int k = 1;
while (p != NULL && k < i - 1) {
pr = p;
p = p->link;
k++;
}
if (p == NULL) p = pr; //链的长度比要插入的位置小,p回到链尾
LinkNode* newNode = (LinkNode*)malloc(sizeof(LinkNode));
newNode->data = x;
newNode->link = p->link;
p->link = newNode;
}
return true;
}
bool Remove(LinkList& first, int i, DataType& x) {
LinkNode* q, * p; int k;
if (i == 0) {
printf("%d是无效的删除位置!\n", i);
return false;
}
else if (i == 1) {
q = first;
first = first->link;
}
else {
p = first; k = 1;
while (p != NULL && k < i - 1) {
p = p->link;
k++;
}
if (p == NULL || p->link == NULL) {
printf("%d是无效的删除位置!\n", i);
return false;
}
q = p->link;
p->link = q->link;
}
x = q->data; free(q);
return true;
}
(3)带头结点的单链表
- 位于单链表首元结点之前。头结点data可以不储存任何信息,也可以存放一个特殊的标志或是表长。
- 头结点是第0个结点。
- 这样做的好处是,同一了空表和非空表插入与删除首元结点的操作。也简化了插入与删除的代码的实现。
//LinkList.h
#pragma once
#include<cstdio>
#include<cstdlib>
using namespace std;
typedef int DataType;
typedef struct node {
DataType data;
struct node* link;
}LinkNode, *LinkList;
//*LinkList 应该是一个结构指针,等效于 typedef node* LinkList
//LinkList0.cpp
#include "LinkList.h"
void initList(LinkList& first) {
first = (LinkNode*)malloc(sizeof(LinkNode));
first->link = NULL;
}
void clearList(LinkList& first) {
LinkNode* q;
while (first->link != NULL) {
q = first->link;
first->link = q->link;
free(q);
}
}
int Length(LinkList& first) {
LinkNode* p = first->link;
int cnt = 0;
while (p != NULL) {
p = p->link;
cnt++;
}
return cnt;
}
LinkNode* Search(LinkList& first, DataType x) {
LinkNode* p = first->link;
while (p != NULL && p->data != x) p = p->link;
return p;
}
LinkNode* Locate(LinkList& first, int i) {
if (i < 0) return NULL;
LinkNode* p = first;
int k = 0;
while (p != NULL && k < i) {
p = p->link;
k++;
}
return p;
}
bool Insert(LinkList& first, int i, DataType x) {
if (i < 0) return NULL;
LinkNode* p = Locate(first, i - 1);
if (p == NULL) return false;
LinkNode* s = (LinkNode*)malloc(sizeof(LinkNode));
s->data = x;
s->link = p->link;
p->link = s;
return true;
}
bool Remove(LinkList& first, int i, DataType& x) {
LinkNode* p = Locate(first, i - 1);
if (p == NULL || p->link == NULL) {
return false;
}
LinkNode* q = p->link;
p->link = q->link;
x = q->data;
free(q);
return true;
}
void printList(LinkList& first) {
LinkNode* p;
for (p = first->link; p != NULL; p = p->link) printf("%d ", p->data);
printf("\n");
}
(4)单链表的尾递归
- 所谓的尾递归,是指在递归程序中只有一个递归语句且它处在程序的最后。
- 单链表的很多算法都是可以利用尾递归,好处时算法简单,缺点是在空间上有一定的开销(用到递归工作栈)。
- 主程序的调用格式为:LinkNode *p = Search(first -> link, x);
#include "LinkList.h"
LinkNode* Search(LinkNode* first, DataType x) {
if (first == NULL) return NULL;
else if (first->data == x) return first;
else return Search(first->link, x);
}
(5)用尾插法创建一个单链表(仍是尾递归)
- 尾插法,是指每次新结点总是插入到链表的尾端,这样连续插入的结果是链表中的各结点中数据的逻辑顺序与输入数据的顺序完全一致。
- 需要设置一个尾指针,它总是指向新链表中的最后一个结点,新结点链接到它所指链尾结点的后面。last指定为引用型指针。
#include"LinkList0.cpp"
void insertRear(LinkNode*& last, DataType endTag) {
DataType val;
scanf("%d", &val);
if (val == endTag) last = NULL;
else {
last = (LinkNode*)malloc(sizeof(LinkNode));
last->data = val;
insertRear(last->link, endTag);
}
}
void main(void) {
LinkList L;
initList(L);
DataType endTag;
scanf("%d", &endTag);
LinkNode* rear = L;
insertRear(rear->link, endTag);
printList(L);
}
(6)用前插法建立单链表(递归算法)。
- 所谓“前插法”,是指每次新结点总是作为首源结点插入在链表的头结点之中。
- 这使得链表中各结点中数据的逻辑顺序与输入数据的顺序正好相反。
#include "LinkList0.cpp"
void insertFront(LinkList& first, DataType endTag) {
DataType val;
scanf("%d", val);
if (val == endTag) return;
LinkNode* s = (LinkNode*)malloc(sizeof(LinkNode));
s->data = val;
s->link = first->link;
first->link = s;
insertFront(first, endTag);
}
void main(void) {
LinkList L;
initList(L);
DataType endTag;
scanf("%d", &endTag);
insertFront(L, endTag);
printList(L);
}
(7)使用后插法与前插法建立单链表的非递归算法
#include "LinkList0.cpp"
void InsertRear(LinkList& first) {
LinkNode* newNode, * last;
DataType val, endTag;
last = first;
scanf("%d", &endTag);
scanf("%d", &val);
while (val != endTag) {
newNode = (LinkNode*)malloc(sizeof(LinkNode));
newNode->data = val;
last->link = newNode;
last = newNode;
scanf("%d", &val);
}
last->link = NULL;
}
void insertFront(LinkList& first) {
LinkNode* newNode;
DataType val, endTag;
scanf("%d", &endTag);
scanf("%d", &val);
while (val != endTag) {
newNode = (LinkNode*)malloc(sizeof(LinkNode));
newNode->data = val;
newNode->link = first->link;
first->link = newNode;
scanf("%d", &val);
}
}
3.用有序链表表示集合
(1)建立
#include<cstdio>
#include<cstdlib>
using namespace std;
typedef int EleType;
typedef struct node {
EleType data;
struct node* link;
}SetNode, *LinkedSet;
void initSet(LinkedSet& S) {
S = (SetNode*)malloc(sizeof(SetNode));
S->link = NULL;
}
(2)查找
SetNode* Contains(LinkedSet& S, EleType x) {
SetNode* p = S->link;
while (p != NULL && p->data < x) p = p->link;
if (p != NULL && x == p->data) return p;
else return NULL;
}
(3)插入
bool addMember(LinkedSet& S, EleType x) {
SetNode* p = S->link, * pr = S;
while (p != NULL && p->data < x) {
p = p->link;
pr = pr->link;
}
if (p != NULL && p->data == x) return false;
SetNode* q = (node*)malloc(sizeof(SetNode));
q->data = x;
q->link = p, pr->link = q;
return true;
}
(4)删除
bool delMember(LinkedSet& S, EleType x) {
SetNode* p = S->link, * pr = S;
while (p != NULL && p->data < x) {
pr = p;
p = p->link;
}
if (p != NULL && p->data == x) {
pr->link = p->link;
free(p);
return true;
}
else return false;
}
(5)并集
void Merge(LinkedSet& LA, LinkedSet& LB, LinkedSet& LC) {
SetNode* pa = LA->link, * pb = LB->link;
SetNode* p, * pc = LC;
while (pa != NULL && pb != NULL) {
pc->link = (SetNode*)malloc(sizeof(SetNode));
pc = pc->link;
if (pa->data > pb->data) {
pc->data = pb->data, pb = pb->link;
}
else if (pa->data < pb->data) {
pc->data = pa->data, pa = pa->link;
}
else {
pc->data = pa->data;
pa = pa->link, pb = pb->link;
}
p = (pa != NULL) ? pa : pb;
}
while (p != NULL) {
pc->link = (SetNode*)malloc(sizeof(SetNode));
pc = pc->link;
pc->data = p->data;
p = p->link;
}
pc->link = NULL;
}
4.循环单链表
(1)特点
- 链表尾结点的 link 域中不是NULL,而是存放了一个指向链表始结点的指针。
- 只要知道表中任何一个结点的地址,就能遍历表中其他任一结点。
- 判断p是否到达链表的链尾时,不是判断是否 p->link == NULL,而是判断是否 p->link == first。
- 在实际应用中长使用不带头结点的循环单链表。
(2)实现:
//CircList.h
#pragma once
#include<cstdio>
#include<cstdlib>
typedef int DataType;
typedef struct node {
DataType data;
struct node* link;
}CircNode, * CircList;
//CircList.cpp
#include "CircList.h"
void createCircList(CircList& first, CircList& rear, DataType A[], int n) {
first = (CircNode*)malloc(sizeof(CircNode));
first->data = A[0]; first->link = first;
rear = first;
CircNode* s;
for (int i = 1; i < n; i++) {
s = (CircNode*)malloc(sizeof(CircNode));
s->data = A[i];
s->link = rear->link;
rear->link = s;
rear = s;
}
}
bool Insert(CircNode*& first, int i, DataType x) {
if (i < 1) return false;
CircNode* p = first, * q;
int k = 1;
if (i == 1) while (p->link != first) p = p->link;
else while (p->link != first && k < i - 1) {
p = p->link;
k++;
}
q = (CircNode*)malloc(sizeof(CircNode));
q->data = x, q->link = p->link, p->link = q;
if (i == 1) first = q;
return true;
}
bool Remove(CircNode*& first, int i, DataType &x) {
if (i < 1) return false;
CircNode* p = first, * q;
int k = 1;
if (i == 1)while (p->link != first) p = p->link;
else while (p->link != first && k < i - 1) {
p = p->link;
k++;
}
if (k < i - 1) return false;
q = p->link, p->link = q->link;
x = q->data;
free(q);
if (i == 1) first = p->link;
return true;
}
(3)约瑟夫环
void printCircList(CircList& first) {
CircNode* p = first;
printf("%d ", p->data);
for (p = p-> link; p != first; p = p->link) printf("%d ", p->data);
printf("\n");
}
void Josephus(CircList& Js, int n, int m) {
CircNode* p = Js, * pr = NULL;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < m - 1; j++) {
pr = p, p = p->link;
}
printf("出队的人是%d\n", p->data);
pr->link = p->link;
free(p);
p = pr->link;
}
printf("最后优胜者是%d\n", p->data);
}
void main() {
CircList clist;
int n, m;
printf("输入游戏人数和报数间隔:\n");
scanf("%d%d", &n, &m);
clist = (CircNode*)malloc(sizeof(CircNode));
clist->data = 1, clist->link = clist;
for (int i = 2; i <= n; i++) Insert(clist, i, i);
printCircList(clist);
Josephus(clist, n, m);
}
四、其他链表
1.双向链表
(1)特点
- 使用双向链表的目的是为了解决在链表中访问直接前驱和直接后继的问题。
- 双向链表的每个结点都有两个链指针,一个指向结点的直接前驱,一个指向结点的直接后继。
- 这样无论是前驱方向查找还是后继方向查找,其时间复杂度只有O(1)。
//DblList.cpp
#include "DblList.h"
void initDblList(DblList& first) {
first = (DblNode*)malloc(sizeof(DblNode));
first->lLink = first;
first->rLink = first;
}
void createListR(DblList& first, DataType A[], int n) {
DblNode* s, * r = first;
for (int i = 0; i < n; i++) {
s = (DblNode*)malloc(sizeof(DblNode));
s->data = A[i], s->freq = 0;
r->rLink = s;
s->lLink = r, s->rLink = first;
first->lLink = s;
r = s;
}
}
void printList(DblList first, bool d) {
DblNode* p = d ? first->rLink : first->lLink;
while (p != first) {
printf("%d", p->data);
p = d ? p->rLink : p->lLink;
if (p != first) printf(" ");
}
printf("\n");
}
DblNode* Search(DblList& first, DataType x, int d) {
DblNode* p = (d == 0) ? first->lLink : first->rLink;
while (p != first) {
p = (d == 0) ? p->lLink : p->rLink;
}
return (p != first) ? p : NULL;
}
DblNode* Locate(DblList& first, int i, int d) {
DblNode* p = (d == 0) ? first->lLink : first->rLink;
int k = 1;
while (p != first && k < i) {
p = (d == 0) ? p->lLink : p->rLink;
k++;
}
if (p != first) return p;
return NULL;
}
bool Insert(DblList& first, DataType x, int i, int d) {
DblNode* p = (d == 0) ? first->lLink : first->rLink;
if (p == NULL) return false;
p = Locate(first, i - 1, d);
DblNode* pl = (d == 0) ? p->lLink : p->rLink;
DblNode* q = (DblNode*)malloc(sizeof(DblNode));
q->data = x;
if (d == 0) {
p->lLink = q; pl->rLink = q;
q->lLink = pl; q->rLink = p;
}
else {
p->rLink = q; pl->lLink = q;
q->rLink = pl; q->rLink = p;
}
return true;
}
2.静态链表
(1)特点:
- 采用一维数组作为链表的储存结构,这就是静态链表。
- 静态链表中为数组的每一个元素附加一个链接指针。
- 处理时可以不改变各元素的物理位置,只要重新链接就能改变这些元素的逻辑顺序。
- 静态链表中的每个结点由两个域组成:data域存储数据;link域存放“指针”。不过,这些“指针”其实只是数组的下标,它给出逻辑上下一个结点在数组中的位置。
//StaticLinkList.h
#pragma once
#include<climits>
#include<cstdio>
#include<cstdlib>
using namespace std;
#define maxSize 100
#define maxValue INT_MAX
typedef int DataType;
typedef struct {
DataType data;
int link;
}SLNode;
typedef struct {
SLNode elem[maxSize + 1];
}StaticLinkList;
五、一元多项式及其运算
//Polynomial.h
#pragma once
#include<cstdio>
#include<cstdlib>
#include<cmath>
using namespace std;
const double EPS = 1e-6;
typedef struct node {
double coef;
int exp;
struct node* link;
}Term, *Polynomial;
//*Term 和 Polynomial可以认为是等价的。
#include "Polynomial.h"
void Input(Polynomial& PL, double C[], int E[], int n) {
Polynomial newTerm, p, pre;
int e;
double c;
while (1) {
scanf("%g%d", &c, &e);
if (e < 0) break;
p = PL->link;
pre = PL;
while (p != NULL && p->exp > e) {
pre = p;
p = p->link;
}
if (p != NULL && p->exp == e) {
printf("已有与指数%d相等的项,输入作废\n", e);
}
else {
newTerm = (Term*)malloc(sizeof(Term));
newTerm->coef = c, newTerm->exp = e;
newTerm->link = p;
pre->link = newTerm;
}
}
}
void Output(Polynomial& PL) {
Polynomial p;
bool h = true;
printf("The polynomial is:\n");
for (p = PL->link; p != NULL; p = p->link) {
if (h == 1) {
if (p->coef < 0) printf("-");
h = 0;
}
else if (p->coef > 0) printf("+");
else printf("-");
if (p->exp == 0 || fabs(p->coef) != 1) {
printf("%g", fabs(p->coef));
}
}
printf("\n");
}
double calcValue(Polynomial& PL, double x) {
Polynomial p = PL->link;
double value = p->coef;
int e = p->exp;
p = p->link;
while (p != NULL) {
value = value * pow(x, e - p->exp) + p->coef;
e = p->exp; p = p->link;
}
return value * pow(x, e);
}
void AddPolynomial(Polynomial& A, Polynomial& B, Polynomial& C) {
Term* pa, * pb, * pc, * p, * s;
double temp;
pc = C;
pa = A->link;
pb = B->link;
while (pa != NULL && pb != NULL) {
if (pa->exp == pb->exp) {
temp = pa->coef + pb->coef;
if (fabs(temp) > EPS) {
s = (Term*)malloc(sizeof(Term));
pc->link = s;
pc = pc->link;
pc->coef = temp, pc->exp = pa->exp;
}
}
else {
s = (Term*)malloc(sizeof(Term));
pc->link = s; pc = pc->link;
if (pa->exp > pb->exp) {
pc->exp = pa->exp;
pc->coef = pa->coef;
pa = pa->link;
}
else {
pc->exp = pb->exp;
pc->coef = pb->coef;
pb = pb->link;
}
}
p = (pa != NULL) ? pa : pb;
while (p != NULL) {
pc->link = (Term*)malloc(sizeof(Term));
pc = pc->link;
pc->coef = p->coef; pc->exp = p->coef;
p = p->link;
}
pc->link = NULL;
}
}
void MultPolynomial(Polynomial& A, Polynomial& B, Polynomial& C) {
Term* pa, * pb, * t, * pr, * p, * q;
pa = A->link;
t = C;
while (pa != NULL) {
pb = B->link;
while (pb != NULL) {
int ke = pa->exp + pb->exp;
int kc = pa->coef * pb->coef;
pr = t; p = pr->link;
while (p != NULL && p->exp > ke) {
pr = p;
p = p->link;
}
if (p == NULL || p->exp < ke) {
q = (Term*)malloc(sizeof(Term));
q->coef = kc;
pr->link = q, q->link = p;
}
else {
p->coef += kc;
if (p->coef < EPS) {
pr->link = p->link;
free(p);
}
}
pb = pb->link;
}
pa = pa->link; t = t->link;
}
}