文章目录
数据结构
第一章: 线性表
1.1 线性表的定义
1.2 顺序表的定义
1.1.1 静态分配
#include <stdio.h>
#define MaxSize 10 //定义最大长度
typedef struct {
int data[MaxSize]; //用静态的"数组"存放数据元素
int length; //顺序表的当前长度
}SqList; //顺序表的类型定义
//基本操作---初始化一个顺序表
void InitList(SqList &L) {
for(int i = 0; i < MaxSize; i++)
L.data[i] = 0; //将所有数据元素设置为默认初始值
L.length = 0; //顺序表初始长度为0
}
int main() {
SqList; //声明一个顺序表
InitList(L); //初始化顺序表
for(int i = 0; i < MaxSize; i++)
printf("data[%d]=%d\n", i, L.data[i]);
return 0;
}
1.1.2 动态分配
#include <stdlib.h>
#define InitSize 10 //默认的最大长度
typedef struct {
int *data; // 指示动态分配数组的指针
int MaxSize; //顺序表的最大容量
int length; //顺序表的当前长度
}SeqList;
//初始化
void InitList(SeqList &L) {
//用malloc函数申请一片连续的存储空间
L.data = (int *)malloc(InitSize*sizeof(int));
L.length = 0;
L.MaxSize = InitSize;
}
//增加动态的长度
void IncreaseSize(SeqList &L, int len) {
int *p = L.data;
L.data = (int *)malloc((L.MaxSize + len)*sizeof(int));
for(int i = 0; i < L.length; i++)
L.data[i] = p[i]; //将数据复制到新区域
L.MaxSize = L.MaxSize + len; //顺序表最大长度增加 len
free(p);
}
int main(void) {
SeqList L; //声明一个顺序表
InitList(L); //初始化顺序表
IncreaseSize(L, 5);
return 0;
}
1.3顺序表的基本操作
-
插入操作
#define MaxSize 10 //定义最大长度 typedef struct { int data[MaxSize]; //用静态的"数组"存放数据元素 int length; //顺序表的当前长度 }SqList; //顺序表的类型定义 bool ListInsert(SqList &L, int i, int e) { if(i < 1 || i > L.length) //判断i的范围是否有效 return false; if(L.length >= MaxSize) //当前存储空间已满,不能插入 return false; for(int j = L.length; j >= i; j--) //将第i个元素及以后的元素后移 L.data[j] = L.data[j - 1]; L.data[i - 1] = e; //在位置i处放入e L.length++; //长度加1 return true; }
-
删除操作
bool ListDelete(SqList &L, int i, int &e) { if(i <1 || i > L.length) //判断i的范围是否有效 return false; e = L.data[i - 1]; //将被删除的元素赋值给e for(int j = i; j < L.length; j++) // 将第i个位置的元素迁移 L.data[j - 1] = L.data[j]; L.length--; // 线性表长度减1 return true; } int main() { SqList L; //声明一个顺序表 InitList(L); //初始化顺序表 int e = -1; if(ListDelete(L, 3, e)) printf("已删除第3个元素,删除元素值为=%d]n", e); else printf("位序i不合法, 删除失败\n"); return 0; }
-
按位查找
#define InitSize 10 //顺序表的初始长度 typedef struct { ElemType *data; //指示动态分配数组的指针 int MaxSize; //顺序表的最大容量 int length; //顺序表的当前长度 } SeqList; //顺序表的类型定义 ElemType GetElem(SeqList L, int i) { return L.data[i - 1]; }
-
按值查找
typedef struct { int *data; //指示动态分配数组的指针 int MaxSize; //顺序表的最大容量 int length; //顺序表的当前长度 }SeqList; // 在顺序表L中查找第一个元素值等于e的元素,并返回其位序 int LocateElem(SeqList L, int e) { for(int i = 0; i < L.length; i++) if(L.data[i] == e) return i + 1; //数组下标为i的元素值等于e,返回其位序i+1 return 0; //退出循环,说明查找失败 }
1.4线性表的链式表示
1.3.1 单链表的定义
typedef struct LNode { //定义单链表结点类型
ElemType data; //每个结点存放一个数据元素
struct LNode *next; //指针指向下一个结点
}LNode, *LinkList;
单链表的两种实现方式:
-
不带头结点的单链表
typedef struct LNode { //定义单链表结点类型 ElemType data; //每个结点存放一个数据元素 struct LNOde *next; //指针指向下一个结点 }LNode, *LinkList; //初始化一个空的单链表 bool InitList(LinkLIst &L) { L = NULL; //空表,暂时还没有任何结点 return true; } void test() { LinkList L; //声明一个指向单链表的指针 //初始化一个空表 InitList(L); } // 判断单链表是否为空 bool Empty(LinkList L) { if (L == NULL) return true; else return false; }
-
带头结点的单链表
typedef struct LNode { //定义单链表结点类型 ElemType data; //每个结点存放一个数据元素 struct LNode *next; //指针指向下一个结点 }LNode, *LinkLIst; //初始化一个单链表 bool InitList(LinkList &L) { L = (LNode *)malloc(sizeof(LNode)); //分配一个头结点 if (L==NULL) return false; L->next = NULL; //头结点之后暂时还没有结点 return true; } void test() { LinkList L; //声明一个指向单链表的指针 //初始化一个空表 InitList(L); } //判断单链表是否为空 bool Empty(LinkList L) { if (L->next == NULL) return true; else return false; }
1.3.2 单链表的插入操作
-
按位序插入(带头结点)
typedef struct LNOde { ElemType data; struct LNOde *next; }LNode, *LinkList; //在第i个位置插入元素e(带头结点) bool ListInsert(LinkList &L, int i, ElemType e) { if(i <1) return false; LNode *p; //指针p指向当前扫描到的结点 int j = 0; //当前p指向的是第几个结点 p = L; //L指向头结点, 头结点是第0个结点(不存数据) while (p != NULL && j < i -1) //循环找到第 i-1 个结点 p = p->next; j++; if(p==NULL) //i值不合法 return false; LNOde *s = (LNode *)malloc(sizeof(LNode)); s->data = e; s->next = p->next; p->next = s; //将结点s连到p之后 return true; //插入成功 }
-
按位序插入(不带头结点)
typedef struct LNode { ElemType data; struct LNOde *next; }LNode, *LinkList; bool ListInsert(LinkList &L, int i, ElemType e) { if(i < 1) return false; if(i==1) { LNOde *s = (LNode *)malloc(sizeof(LNode)); s->data = e; s->next = L; L = s; //头指针指向新结点 return true; } LNode *p; //指针p指向当前扫描到得结点 int j = 1; //当前p指向的是第几个结点 p = L; //p指向第1个结点 while (p!=NULL && j<i-1) {//循环找到第 i-1 个结点 p = p->next; j++; } if(p==NULL) //i值不合法 return false; LNode *s = (LNode *)malloc(sizeof(LNode)); s->data = e; s->next = p->next; p->next = s; return true; // 插入成功 }
-
指定结点的后插操作
typedef struct LNode { ElemType data; struct LNode *next; }LNode, *LinkList; //在第i个位置插入元素e bool ListInsert(LinkList &L, int i, ElemType e) { if(i < 1) return false; LNode *p; //指针p指向当前扫描到的结点 int j = 0; //当前p指向的是第几个结点 p = L; //L指向头结点,头结点是第0个结点 while (p!=NULL && j < i-1) { //循环找到第 i-1 个结点 p = p->next; j++; } return InsertNextNode(p, e); } //后插操作: 在p结点之后插入元素e bool InsertNextNode (LNOde *p, ElemType e) { if(p==NULL) return false; LNOde *s = (LNode *)malloc(sizeof(Lnode)); if (s==NULL) //内存分配失败 return false; s->data = e; //用结点s保存数据元素e s->next = p->next; p->next = s; //将结点s连到p之后 return true; }
-
指定结点的前插操作
//前插操作: 在p结点之前插入结点s bool InsertPriorNode (LNode *p, LNode *s) { if (p==NULL || s==NULL) return false; s->next = p->next; p->next = s; //s连到p之后 ElemType temp = p-data; //交换数据域部分 p->data = s->data; s->data = temp; return true; }
1.3.3 单链表的删除操作
-
按位序删除(带头结点)
typedef struct LNOde { ElemType data; struct LNode *next; }LNOde, *LinkList; bool ListDelete(LinkList &L, int i, ElemType &e) { if(i<1) return false; LNode *p; //指针p指向当前扫描到的结点 int j = 0; //当前p指向的是第几个结点 p = L; //L指向头结点,头结点是第0个结点 while(p!=NULL && j<i-1) { //循环找到第 i-1 个结点 p=p->next; j++; } if(p==NULL) //i值不合法 return false; if(p->next == NULL) //第i-1个结点之后已无其他结点 return false; LNode *q = p->next; //令q指向被删除结点 e = q->data; //用e返回元素的值 p->next = q->next; //将*q结点从链中"断开" free(q); //释放结点的存储空间 return true; //删除成功 }
-
指定结点删除
// 删除指定结点p bool DeleteNode (LNOde *p) { if (p==NULL) return false; LNode *q = p->next; //令q指向*p的后继结点 p->data = p->next->data; //和后继结点交换数据域 p->next = q->next; //将*q结点从链中"断开" free(q); //释放后继结点的存储空间 return true; }
1.2.4 单链表的查找
-
按位查找
LNode * GetElem(LinkList L, int i) { if(i<0) return NULL; LNode *p; //指针p指向当前扫描到的结点 int j = 0; //当前p指向的是第几个结点 p = L; //L指向头结点,头结点是第0个结点 while(p!=NULL && j<i) { //循环找到第 i 个结点 p=p->next; j++; } return p; }
-
按值查找
LNode * LocateElem(LinkList L, ElemType e) { LNode *p = L->next; //从第1个结点开始查找数据域为e的结点 while(p != NULL && p->data != e) p = p->next; return p; //找到后返回该结点指针,否则返回NULL }
1.2.4 求单链表的长度
int Length(LinkList L) { int len = 0; //统计表长 LNode *p = L; while (p->next != NULL){ p = p->next; len++; } return len; }
-
1.3.4 单链表的建立
-
头插法建立单链表
LinkList List_HeadInsert(LinkList &L){ //逆向建立单链表 LNode *s; int x; L = (LinkList)malloc(sizeof(LNode)); //创建头结点 L->next = NULL; //初始为空链表 scanf("%d", &x); //输入结点的值 while(x!=9999) { s = (LNode*)malloc(sizeof(LNode)); //创建新结点 s->data = x; s->next = L->next; L->next = s; //将新结点插入表中,L为头指针 scanf("%d", &x); } return L; }
-
尾插法建立单链表
LinkList List_TailInsert(LinkList &L) { //正向建立单链表 int x; //设ElemType 为整型 L=(LinkList)malloc(sizeof(LNode)); //建立头结点 LNode *s, *r = L; //r尾表尾指针 scanf("%d", &x); //输入结点的值 while(x!=9999) { //输入9999表示结束 s = (LNode *)malloc(sizeof(LNode)); s->data = x; r->next = s; r=s; //r指向新的表尾结点 scanf("%d", &x); } r->next = NULL; //尾结点指针置空 return L; }
1.3.5 单链表的逆置
LNode *Inverse(LNode *L) {
LNode *P, *q;
p = L->next; //p指针指向第一个结点
L->next = NULL; //头结点指向NULL
while (p != NULL) {
q = p;
p = p-next;
q->next = L->next;
L->next = q;
}
return L;
}
1.3.6 双链表
-
双链表的存储结构
typedef struct DNode { //定义双链表结点类型 ElemType data; //数据域 struct DNode *prior, *next; //前驱和后继指针 }DNode, *DLinklist;
-
双链表的初始化
bool InitDLinkList(DLinklist &L) { L = (LNode *) malloc(sizeof(DNode)); //分配一个头结点 if (L==NULL) //内存不足,分配失败 return false; L->prior = NULL; //头结点的 prior 永远指向 NULL L->next = NULL; //头结点之后暂时还没有结点 } void testDLinkList() { //初始化双链表 DLinklist L; InitDLinkList(L); } //判断双链表是否为空 bool Empty(DLinklist L) { if (L->next == NULL) return true; else return false; }
-
双链表的插入
bool InsertNextDNode(DNode *p, DNode *s) { if (p==NULL || s==NULL) return false; s->next = p->next; if (p->next != NULL) //如果p结点有后继结点 p->next->prior = s; s->prior = p; p->next = s; return true; }
-
双链表的删除
//删除p结点的后继结点 bool DeleteNextDNode(DNode *p) { if (p==NULL) return false; DNode *q = p->next; //找到p的后继结点q if (q==NULL) return false; //p没有后继 p->next = q->next; if (q->next != NULL) //q结点不是最后一个结点 q->next->prior=p; free(q); //释放结点空间 return true; } //销毁表 void DestoryList(DLinklist &L) { //循环释放各个数据结点 while(L->next != NULL) DeleteNextNode(L); free(L); //释放头结点 L=NULL; //头指针指向NULL }
-
双链表的遍历
//向后遍历 while(p!=NULL) { p = p->next; } //向前遍历 while(p!=NULL) { p = p->prior; } //向前遍历(跳过头结点) while(p->prior != NULL) { p = p->prior; }
1.3.7 循环链表
-
循环单链表
最后一个结点的指针不是NULL,而是指向头结点
typedef struct LNode{ //定义单链表结点类型 ElemType data; //每个结点存放一个数据元素 struct LNode *next; //指针指向下一个结点 } //初始化一个循环单链表 bool InitList(LinkList &L) { L = (LNode *)malloc(sizeof(LNOde)); //分配一个头结点 if (L==NULL) //内存不足,分配失败 return false; L->next = L; //头结点next指向头结点 return true; } //判断循环单链表是否为空 bool Empty(LinkList L) { if (L->next == L) return true; else return falsse; }
-
循环双链表
表头结点的prior指向表尾结点,表尾结点的next指向头结点
typedef struct DNode { ElemType data; struct DNode *prior, *next; }DNode, *DLinklist; //初始化空的循环双链表 bool InitDLinkList(DLinklist &L) { L = (DNode *)malloc(sizeof(DNode)); //分配一个头结点 if(L==NULL) //内存不足,分配失败 return false; L->prior = L; //头结点的 prior 指向头结点 L->next = L; //头结点的 next 指向头结点 return true; } void testDLinkList() { //初始化循环双链表 DLinkList L; InitDLinkList(L); } //判断循环双链表是否为空 bool Empty(DLinkList L) { if (L->next == L) return true; else return false; } //判断结点p是否为循环单链表的表尾结点 bool isTail(DLinklist L, DNode *p){ if (p->next==L) return true; else return false; } //在p结点之后插入结点 bool InsertNextDNode (DNode *p, DNode *s) { s->next = p->next; //将结点*s插入到结点*p之后 p->next->prior=s; s->prior=p; p->next=s; } //删除p的后继结点q p->next = q->next; q->next->prior = p; free(q);
1.3.8 静态链表
#define MaxSize 10 //静态链表的最大长度
typedef struct { //静态链表结构类型的定义
ElemType data; //存储数据元素
int next; //下一个元素的数据下标
} SLinkList[MaxSize];
void testSLinkList() {
SLinkList a;
}
<==============>等价于
#define MaxSize 10 //静态链表的最大长度
struct Node{ //静态链表结构类型的定义
ElemType data; //存储数据元素
int next; //下一个元素的数组下标
};
typedef struct Node SLinkList[MaxSize];
void testSLinkList() {
struct Node a[MaxSize];
}
第二章: 栈和队列
2.1 栈
2.1.1 栈的基本概念
2.1.2 栈的顺序存储
-
顺序栈的定义
#define MaxSize 10 //定义栈中元素的最大个数 typedef struct { ElemType data[MaxSize]; //静态数组存放栈中元素 int top; //栈顶指针 } SqStack;
-
栈的基本操作
//初始化栈 void InitStack(SqStack &s) { s.top = -1; //初始化栈顶指针 } void testStaack() { SqStack S; //声明一个顺序栈(分配空间) InitStack(S); } //判断栈空 bool StackEmpty(SqStack s) { if(S.top==-1) //栈空 return true; else return false; } //进栈操作 bool Push(SqStack &s, ElemType x) { if(S.top==MaxSize-1) //栈满,报错 return false; S.data[++S.top] = x; return true; } //出栈操作 bool Pop(SqStack &s, ElemType &x) { if(S.top==-1) //栈空,报错 return false; x = S.data[S.top--]; return true; } //读栈顶元素 bool GetTop(SqStack S, ElemType &x) { if(S.top==-1) //栈空,报错 return false; x = S.data[S.top]; //x记录栈顶元素 return true; }
-
共享栈
#define MaxSize 10 //定义栈中元素的最大个数 typedef struct { ElemType data[MaxSize]; //静态数组存放栈中元素 int top0; //0号栈顶指针 int top1; //1号栈顶指针 } ShStack; //初始化栈 void InitStack(ShStack &s) { S.top0 = -1; //初始化栈顶指针 S.top1 = MaxSize; } 栈满条件: top0 + 1 == top1
2.1.3 栈的链式存储
-
链栈的定义
typedef struct LinkNode { ElemType data; //数据域 struct LinkNode *next; //指针域 } *LiStack; //栈类型定义
-
栈的基本操作
#include<stdio.h> struct Linknode { int data; //数据域 LinkNode *next; //指针域 }Linknode, *LiStack; typedef Linknode *Node; //结点结构体指针变量 typedef Node List; //结点结构体头指针变量 //1.初始化 void InitStack(LiStack &L) { //L为头指针 L = new Linknode; L->next = NULL; } //1.判断栈空 bool isEmpt(LiStack &L) { if(L->next == NULL) return true; else return false; } //2.进栈 void pushStack(LiStack &L, int x) { Linknode s; //创建存储新元素的结点 s = new Linknode; s->data = x; //头插法 s->next = L->next; L-next = s; } //3.出栈 bool popStack(LiStack &L, int &x) { Linknode s; if(L->next == NULL) //栈空不能出栈 return false; s = L-next; x = s->data; L->next = L->next->next; delete(s); return true; }
2.2 队列
2.1.1 队列的定义
只允许在一端进行插入,在另一端删除的线性表
2.1.2 队列的顺序存储
-
队列的基本操作
#define MaxSize 10 //定义队列中元素的最大个数 typedef struct{ ElemType data[MaxSize]; //用静态数组存放队列元素 int front, rear; //对头指针和队尾指针 } SqQueue; //初始化队列 void InitQueue(SqQueue &Q) { //初始时 对头、队尾指针指向0 Q.rear = Q.front = 0; } void teatQueue() { //声明一个队列 SqQueue Q; InitQueue(Q); } //判断队列是否为空 bool QueueEmpty(SqQueue Q) { if(Q.rear==Q.front) //对空条件 return true; else return false; }
-
循环队列
//入队 bool EnQueue(SqQueue &Q,ElemType x) { if(Q.rear+1)%MaxSize==Q.front) return false; //队满则报错 Q.data[Q.rear] = x; //新元素插入队尾 Q.rear = (Q.rear+1)%MaxSize; //队尾指针加1取模 } //出队 bool DeQueue(SqQueue &Q, ElemType &x) { if(Q.rear==Q.front) return false; //对空则报错 x=Q.data[Q.front]; Q.front=(Q.front+1)%MaxSize; return true; } //获得对头元素的值,用x返回 bool GetHead(SqQueue Q, ElemType &x) { if(Q.rear==Q.front) return false; //队空则报错 x = Q.data[Q.front]; return true; }
-
判断队列队空还是队满的情况
方案一: -牺牲一个单元来区分队空还是队满,入队是少用一个队列单元 -队满条件: (Q.rear+1)%MaxSize==Q.front; -队空条件: Q.front==Q.rear; 方案二: -类型中增设表示元素个数的数据成员 -队满条件: Q.size==MaxSize; -队空条件: Q.size==0; 方案三: -类型中增设tag数据成员 -队满条件: Q.front == Q.rear && tag == 1 -队空条件: Q.front == Q.rear && tag == 0
2.1.3 队列的链式存储
-
队列的初始化
typedef struct LinkNode { ElemType data; struct LinkNode *next; }LinkNode; typedef struct { LinkNode *front, *rear; }LinkQueue; //初始化队列 void InitQueue(LinkQueue &Q){ //初始时 front、rear 都指向头结点 Q.front=Q.rear=(LinkNode*)malloc(sizeof(LinkNode)); Q.front->next = NULL; } void testLinkQueue() { LinkQueue Q; //声明一个队列 InitQueue(Q); //初始化队列 } //判断队列是否为空 bool IsEmpty(LinkQueue Q) { if(Q.front==Q.rear) return true; else return false; }
-
队列的基本操作
//入队(带头结点) void EnQueue(LinkQueue &Q, ElemType x) { LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode)); s->data = x; s->next=NULL; Q.rear->next=s; //新结点插入到rear之后 Q.rear=s; //修改表尾指针 } //入队(不带头) void EnQueue(LinkQueue &Q, ElemType x) { LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode)); s->data = x; s->next = NULL; if (Q.front == NULL) { //在空队列中插入第一个元素 Q.front = s; //修改队头队尾指针 Q.rear = s; }else { Q.rear->next = s; //新结点插入到rear结点之后 Q.rear=s; //修改 rear 指针 } } //出队(带头结点) bool DeQueue(LinkQueue &Q, ElemType &x) { if(Q.front==Q.rear) return false; //空队 LinkNode *p = Q.front->next; x = p->data; //用变量x返回队头元素 Q.front->next = p->next; //修改头结点的next指针 if(Q.rear==p) Q.rear = Q.front; //修改rear指针 free(p); //释放结点空间 return true; } //出队(不带头结点) bool DeQueue(LinkQueue &Q, ElemType &x) { if(Q.front==NULL) return falsse; //空队 LinkNode *p = Q.front; //p指向此次出队的结点 x = p->data; //用变量x返回队头元素 Q.front = p->next; //修改front指针 if(Q.rear==p) { //此次是最后一个结点出队 Q.front = NULL; //front指向NULL Q.rear = NULL; //rear指向NULL } free(p); //释放结点空间 return true; }
2.1.4 双端队列
双端队列是只允许两端都可以进行入队和出队操作的队列
- 双端队列允许从两端插入、两端删除的线性表
- 如果只使用其中一端的插入、删除操作,则等同于栈
- 输入受限的双端队列: 允许一端插入,两端删除的线性表
- 输出受限的双端队列: 允许两端插入,一端删除的线性表
2.3 栈的应用
2.2.1 栈在括号匹配中的应用
#define MaxSize 10 //定义栈中元素的最大个数
typedef struct {
char data[MaxSize]; //静态数组存放栈中元素
int top; //栈顶指针
} SqStack;
//初始化栈
void InitStack(SqStack &S)
//判断栈是否为空
bool StackEmpty(SqStack S)
//新元素入栈
bool Push(SqStack &S, char x)
//栈顶元素出栈,用x返回
bool Pop(SqStack &S, char &x)
bool breacketCheck(char str[], int length) {
SqStack S;
InitStack(S); //初始化一个栈
for(int i = 0; i < length; i++){
if (str[i]=='*(' || str[i]=='[' str[i]=='{'){
Push(S,str[i]); //扫描左括号,入栈
} else {
if (StackEmpty(S)) //扫描到有括号,且当前栈空
return false; //匹配失败
char topElem;
Pop(S, topElem); //栈顶元素出栈
if(str[i]==')' && topElem!='(')
return false;
if(str[i]==']' && topElem!='[')
return false;
if(str[i]=='}' && topElem!='{')
return false;
}
}
return StackEmpy(S); //检索完全部括号后,栈空说明匹配成功
}
2.2.2 栈在表达式求值中的应用
2.2.3 栈在递归中的应用
2.4 队列的应用
2.3.1 树的层次遍历
2.3.2 图的广度优先遍历
2.3.3 队列在操作系统中的应用
第三章: 串
3.1串的定义和存储结构
3.1.1 顺序存储
-
顺序存储的初始化
#define MAXLEN 255 //预定义最大串长为255 typedef struct{ char ch[MAXLEN]; //每个分量存储一个字符 int length; //串的实际长度 }SString; typedef struct{ char *ch; //按串长分配存储区,ch指向串的基地址 int length; //串的长度 }HString; HString S; S.ch = (char *)malloc(MAXLEN * sizeof(char)); S.length = 0;
-
顺序存储的基本操作
#define MAXLEN 255 //预定义最大串为255 typedef struct{ char ch[MAXLEN]; //每个分量存储一个字符 int length; //串的实际长度 } //求子串 bool SubString (SString &Sub, SString S, int pos, int len) { //子串范围越界 if (pos+len-1 > S.length) return false; for (int i = pos; i < pos+len; i++) Sub.ch[i-pos+1] = S.ch[i]; Sub.length = len; return true; } //比较操作 int StrCompare(SString S, SString T) { for(int i = 1; i<=S.length && i <=T.length; i++){ if(S.ch[i] != T.ch[i]) return S.ch[i]-T.ch[i]; } //扫描过的所有字符都相同,则长度长的串更大 return S.length-T.length; } //定位操作 int Index(SString S, SString T){ int i = 1, n = StrLength(S), m=StrLength(T); SString sub; //用于暂存子串 while(i <= n-m+1){ SubString(sub, S, i, m); if(StrCompare(sub, T)!=0) ++i; else return i; //返回子串在主串的位置 } return 0; //S中不存在与T相等的子串 }
3.1.2 串的链式存储
typedef struct StringNode {
char ch[4];
struct stringNode *next;
} stringNode, *String;
3.1.3 串的堆存储
typedef struct {
char *ch; //按串长分配存储区,ch指向串的基地址
int length; //串的长度
} HString;
3.2 串的模式匹配
模式匹配: 子串的定位操作称为串的模式,求的是子串在主串中的位置
3.2.1 朴素模式匹配算法
int Index(SString, SString T) {
int i = 1, j = 1;
while(i <= S.length && j <= T.length) {
if(S.ch[i]==T.ch[j]) {
++i;++j; //继续比较后继字符
}else {
i = i-j+2;
j=1; //指针后退重新开始匹配
}
}
if(j>T.length)
return i-T.length;
else
return 0;
}
3.2.2 KMP算法
int Index_KMP(SString S,SString T, int next[]) {
int i = 1, j = 1;
while(i <= S.length && j <= T.length) {
if(j==0 || S.ch[i] == T.ch[j]) {
++i;
++j; //继续比较后继字符
}else {
j = next[j]; //模式串向右移动
}
}
if(j>T.length)
return i-T.length; //匹配成功
else
return 0;
}
3.2.3 求next数组
第四章: 树
4.1 树的基本概念
4.1.1 树的定义
4.1.2 树的基本术语
4.1.3 树的性质
- 结点树=总度数+1
- 度为m的树第i层至多有m^(i-1) 个结点(i>=1)
- 高度为h的m叉树至多有(m^h-1)/(m-1)个结点
- 高度为h的m叉树至少有h个结点
4.2 二叉树的基本概念
4.2.1 二叉树的定义
几种特殊的二叉树:
-1.满二叉树: 一棵高度为h,且含有2^h-1个结点的二叉树
-2.完全二叉树: 当且仅当其每个结点都与高度为h的满二叉树中编号为1~n的结点一一对应时
-3.二叉排序树
-4.平衡二叉树
4.2.2 二叉树的基本性质
1.设非空二叉树中度为0、1和2的结点个数分别为n0、n1和n2,则n0 = n2 + 1
2.二叉树第i层至多有2^i-1个结点(i>=1)
3.高度为h的二叉树至多有2^h-1个结点(满二叉树)
4.具有n个(n>0)结点的完全二叉树的高度为[log2(n+1)]或[log2n]+1
4.2.3 二叉树的存储结构
-
顺序存储
#define MaxSize 100 struct TreeNode { ElemType value; //结点中的数据元素 bool isEmpty; //结点是否为空 }; TreeNode t[MaxSize]; for(int i = 0; i<MaxSize;i++) { t[i].isEmpty = true; } 注: 二叉树的顺序存储中,一定要把二叉树的结点编号与完全二叉树对应起来
-
链式存储
typedef struct BiTNode { ElemType data; //数据域 struct BiTNode *lchild, *rchild; //左、右孩子指针 }BiTNode, *BiTree; //定义一颗空树 BiTree root = NULL; //插入根结点 root = (BiTree) malloc(sizeof(BiTNode)); root->data = {1}; root->lchild = NULL; root->rchild = NULL; //插入新结点 BiTNode *p = (BiTNode *) malloc(sizeof(BiTNode)); p->data = {2}; p->lchild = NULL; p->rchild = NULL; root->lchild = p; //作为根结点的左孩子 //三叉链表 typedef struct BiTNode{ ElemType data; //数据域 struct BiTNode *lchild,*rchild; //左、右孩子指针 struct BiTNode *parent; //父结点指针 }BiTNode, *BiTree; ***注***: n个结点的二叉链表共有n+1个空链域
4.3 二叉树的遍历
4.3.1 先序遍历
typedef struct BiTNode {
ElemType data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
//先序遍历
void PreOrder(BiTree T) {
if(T!=NULL) {
visit(T); //访问根结点
PreOrder(T->lchild); //递归遍历左子树
PreOrder(T->rchild); //递归遍历右子树
}
}
4.3.2 中序遍历
typedef struct BiTNode {
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode, *BiTree;
//中序遍历
void InOrder(BiTree T) {
if(T!=NULL) {
InOrder(T->lchild); //递归遍历左子树
visit(T); //访问根结点
InOrder(T->rchild); //递归遍历右子树
}
}
4.3.3 后序遍历
typedef struct BiTNode {
ElemType data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
//后序遍历
void PostOrder(BiTree T) {
if(T!=NULL)
PostOrder(T->lchild); //递归遍历左子树
PostOrder(T->rchild); //递归遍历右子树
visit(T); //访问根结点
}
4.3.4 层序遍历
//二叉树的结点(链式存储)
typedef struct BiTNode {
char data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
//链式队列结点
typedef struct LinkNode {
BiTNode *data;
struct LinkNode *next;
}LinkNode;
typedef struct {
LinkNode *front,*rear; //队头队尾
}
//层序遍历
void LevelOrder(BiTree T) {
LinkQueue Q;
InitQueue(Q); //初始化辅助队列
BiTree p;
EnQueue(Q, T); //将根结点入队
while(!IsEmpty(Q)) { //队列不空则循环
DeQueue(Q, p); //对头结点出队
visit(p); //访问出队结点
if(p->lchild != NULL)
EnQueue(Q,p->lchild); //左孩子入队
if(p->rchild != NULL)
EnQueue(Q,p->rchild); //右孩子入队
}
4.3.5 由二叉树的遍历序列构造二叉树
- 先序序列 + 中序序列
- 后序序列 + 中序序列
- 层序序列 + 中序序列
4.4 线索二叉树
4.4.1 线索二叉树的定义
//二叉树的结点(链式存储)
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode, *BiTree;
//线索二叉树结点
typedef struct ThreadNode {
ElemType data;
struct ThreadNode *lchild, *rchild;
int ltag,rtag; //左、右线索标志
}ThreadNode, *ThreadTree;
**注**:
-tag==0,表示指针指向孩子
-tag==1,表示指针是"线索"
4.4.2 二叉树线索化
-
中序线索化
//线索化二叉树结点 typedef struct ThreadNode { ElemType data; struct ThreadNode *lchild, *rchild; int ltag,rtag; //左、右线索标志 }ThreadNode, * ThreadTree; //中序遍历二叉树,一遍遍历一遍线索化 void InThread(ThreadTree T) { if(T!=NULL) InThread(T->lchild); //中序遍历左子树 visit(T); //访问根结点 InThread(T->rchild); //中序遍历右子树 } void visit(ThreadNode *q) { if(q->lchild==NULL){ //左子树为空,建立前驱线索 q->lchild=pre; q->ltag=1; } if(pre!=NULL&&pre->rchild==NULL) { pre->rchild=q; //建立前驱结点的后继线索 pre->rtag=1; } pre=q; } //全局变量pre,指向当前访问结点的前驱 ThreadNode *pre = NULL; //中序线索化二叉树T void CreateInThread(ThreadTree T) { pre = NULL; //pre初始为NULL if(T!=NULL){ //非空二叉树才能线索化 InThread(T); //中序线索化二叉树 if(pre->rchild==NULL) pre->rtag=1; //处理遍历的最后一个结点 } }
-
先序线索化
void PreThread(ThreadTree T) { if(T!=NULL) { visit(T); //先处理根结点 if (T->ltag==0) //lchild不是前驱线索 PreThread(T->lchild); PreThread(T->rchild); } } void viste(ThreadNode *q) { if(q->lchild==NULL){ //左子树为空,建立前驱线索 q->lchild = pre; q->ltag = 1; } if(pre!=NULL&&pre->rchild==NULL) { pre->rchild=q; //建立前驱结点的后继线索 pre->rtag=1; } pre=q; } //全局变量pre,指向当前访问结点的前驱 ThreadNode *pre = NULL; //先序线索化二叉树T void CreatePreThread(ThreadTree T) { pre = NULL; //pre初始为NULL if(T!=NULL) { //非空二叉树才能线索化 PreThread(T); //先序线索化二叉树 if(pre->rchild==NULL) pre->rtag = 1; //处理遍历的最后一个结点 } }
-
后序线索化
//后遍历二叉树,一边遍历一边线索化 void PostThread(ThreadTree T) { if(T!=NULL){ PostThread(T->lchild); //后序遍历左子树 PostThread(T->rchild); //后序遍历右子树 visit(T); //访问根结点 } } void visit(ThreadNode *q) { if(q->lchild==NULL){ //左子树为空,建立前驱线索 q->lchild=pre; q->ltag=1; } if(pre!=NULL&&pre->rchild==NULL) { pre->rchild=q; //建立前驱结点的后继线索 pre->rag=1; } pre=q; } //全局变量pre,指向当前访问结点的前驱 ThreadNode *pre = NULL; //后序线索化二叉树T void CreatePostThread(ThreadTree T) { pre = NULL; //pre初始为NULL if(T!=NULL) { //非空二叉树才能线索化 PostThread(T); //后序二叉树才能线索化 if(pre->rchild==NULL) pre->rtag=1; //处理遍历的最后一个结点 } }
4.4.3 线索二叉树找前驱/后继
-
先序线索化二叉树找先序前驱
1.若 p->ltag==1,则next=p->lchild 2.若 p->ltag==0,则p必有左孩子 -1.如果能找到p的父结点,且p是左孩子,p的父结点即为其前驱 -2.如果能找到p的父结点,且p是右孩子,左兄弟为空,p的父结点即为其前驱 -3.如果能找到p的父结点,且p是右孩子,左兄弟非空,p的前驱为左兄弟子树中的最后一个被先序遍历的结点 -4.如果p是根结点,则p没有先序前驱
-
先序线索化二叉树找先序后继
1.若 p->ltag==1,则next=p->rchild 2.若 p->rtag==0,则p必有右孩子 -若p有左孩子,则先序后继为左孩子 -若p没有左孩子,则先序后继为右孩子
-
中序线索二叉树找中序前驱
//找到以p为根的子树中,最后一个被中序遍历的结点 ThreadNode *Lastnode(ThreadNode *p) { //循环找到最右下结点 while(p->rtag==0) p=p->rchild; return p; } //在中序线索二叉树中找到结点p的前驱结点 ThreadNode *Prenode(ThreadNode *p) { //左子树中最右下结点 if(p->ltag==0) return Lastnode(p->lchild); else return p->lchild; } //对中序线索二叉树进行逆向中序遍历 void RevInorder(ThreadNode *T) { for(ThreadNode *p=Lastnode(T); p!=NULL; p=Prenode(p)) visit(p); }
-
中序线索二叉树找中序后继
//找到以p为根的子树中,第一个被中序遍历的结点 ThreadNode *Firstnode(ThreadNode *p) { //循环找到最左下结点 while(p->ltag==0) p=p->lchild; return p; } //在中序线索二叉树中找到结点p的后继结点 ThreadNode *Nextnode(ThreadNode *p) { //右子树中最左下结点 if(p->rtag==0) return Firstnode(p->rchild); else return p->rchild; //rtag==1直接返回后继线索 } //对中序线索二叉树进行中序遍历 void Inoder(ThreadNode *T) { for(ThreadNode *p=Firstnode(T); p!=NULL; p=Nextnode(p)) visit(p); }
-
后序线索二叉树找后序前驱
1.若 p->ltag==1,则pre = p->lchild 2.若 p->ltag==0 -若p右孩子,则后序前驱为右孩子 -若p没有右孩子,则后序前驱为左孩子
-
后序线索二叉树找后序后继
1.若 p->rtag==1,则next=p->rchild 2.若 p->rtag==0 -1.如果能找到p的父结点,且p是右孩子,p的父结点即为后继 -2.如果能找到p的父结点, 且p是左孩子,右兄弟为空,p的父结点即为后继 -3.如果能找到p的父结点,p是左孩子,右兄弟非空,p的后继为右兄弟子树中第一个被后序遍历的结点 -4.如果p是根结点,则p没有后序后继
4.5 树、森林
4.4.1 树的存储结构
-
双亲表示法
#define MAX_TREE_SIZE 100 //树的最多结点数 typedef struct{ //树的结点定义 ElemType data; //数据元素 int parent; //双亲位置域 }PTNode; typedef struct{ //树的类型定义 PTNode nodes[MAX_TREE_SIZE]; //双亲表示 int n; //结点数 }PTree;
-
孩子表示法
struct CENode { int child; //孩子结点在数组中的位置 struct CTNode *next; //下一个孩子 }; typedef struct { ElemType data; struct CTNode *firstChild; //第一个孩子 } CTBox; typedef struct { CTBox nodes[MAX_TREE_SIZE]; int n, r; //结点数和根的位置 } CTree;
-
孩子兄弟表示法
typedef struct CSNode { ElemType data; //数据域 struct CSNode *firstchild, *nextsibling; //第一个孩子和右兄弟指针 }CSNode, *CSTree;
4.4.2 森林和二叉树的转换
4.4.3 树和森林的遍历
-
树的遍历
-
树的先根遍历
void PreOrder(TreeNode *R) { if(R!=NULL) { visit(R); //访问根结点 while(R还是下一个子树T) PreOrder(T); //先根遍历下一棵子树 } }
-
树的后根遍历
void PostOrder(TreeNode *R) { if(R!=NULL){ while(R还有下一个子树T) PostOrder(T); //后根遍历下一棵子树 visit(R); //访问根结点 } }
-
树的层次遍历
1.若数非空,则根结点入队 2.若队列非空,对头元素出队并访问,同时将该元素的孩子一次入队 3.重复2直到队列为空
-
-
森林的遍历
- 先序遍历: 等同于依次对各个树进行先根遍历;也可以准换成与之对应的二叉树进行先序遍历
- 中序遍历:等同于依次对各个树进行后根遍历;也可以转换成与之对应的二叉树进行中序遍历
4.6 树和二叉树的应用
4.6.1 二叉排序树
-
二叉排序树的查找
//二叉排序树的结点 typedef struct BSTNode { int key; struct BSTNode *lchild,*rchild; }BSTNode, *BSTree; //在二叉排序树中查找值为key的结点 BSTNode *BST_Search(BSTree T, int key) { while(T!=NULL&&key!=T->key) { //若树空或等于根结点值 if(key<T->key) T=T->lchild; //小于,则在左子树上查找 else T=T->rchild; //大于,则在右子树上查找 } } //在二叉排序树中查找值为key的结点(递归实现) BSTNode *BSTSearch(BSTree T, int key){ if(T==NULL) return NULL; //查找失败 if(key==T->key) return T; //查找成功 else if(key<T->key) return BSTSearch(T->lchild, key); //在左子树中找 else return BSTSearch(T->rchild, key); //在右子树中找 }
-
二叉排序树的插入
//在二叉排序树插入关键字为k的新结点(递归实现) int BST_Insert(BSTree &T, int k) { if(T==NULL) { T=(BSTree)malloc(sizeof(BSTNode)); T->key = k; T->lchild = T->rchild = NULL; return 1; } else if(k==T->key) return 0; else if(k<T->key) return BST_Insert(T->lchild, k); else return BST_Insert(T->rchild, k); }
-
二叉排序树的构造
//按照str[] 中的关键字序列建立二叉排序树 void Creat_BST(BSTree &T,int str[], int n) { T=NULL; //初始时T为空树 int i = 0; while(i < n) { //依次将每个关键字插入到二叉排序树中 BST_Insert(T, str[i]); i++; } }
-
二叉排序树的删除
4.6.2 平衡二叉树
-
定义: 树上任一结点的左子树和右子树的高度之差不超过1。
平衡因子 = 左子树高-右子树高
-
平衡二叉树的插入
-
调整最小不平衡子树
LL:在A结点的左孩子的左子树中插入导致不平衡 调整: A的左孩子结点右上旋 RR:在A结点的右孩子的右子树中插入导致不平衡 调整: A的右孩子结点左上旋 LR:在A结点的左孩子的右子树中插入导致不平衡 调整: A的左孩子的右孩子,先做上旋再右上旋 RL:在A结点的右孩子的左子树中插入导致不平衡 调整: A的右孩子的左孩子,先右上旋再左上旋
4.6.3 哈夫曼树
第五章: 图
5.1 图的基本概念
5.2 图的存储
5.2.1 邻接矩阵法
#define MaxVertexNum 100 //顶点数目的最大值
typedef struct{
char Vex[MaxVertexNum]; //顶点表
int Edge[MaxVertexNum][MaxVertexNum]; //邻接矩阵,边表
int vexnum,arcnum; //图的当前顶点数和边数
} MGraph;
5.2.2 邻接表法
//边
typedef struct ArcNode{
int adjvex; //边/弧指向哪个结点
struct ArcNode *next; //指向下一条弧的指针
}
//顶点
typedef struct VNode{
VertexType data; //顶点信息
ArcNode *first; //第一条边
}VNode, AdjList[MaxVertexNum];
5.2.3 十字链表
只用于存储有向图
5.2.4 邻接多重表
只用于存储无向图
5.3 图的遍历
5.3.1 广度优先算法
bool visited[MAX_VERTEX_NUM]; //访问标记数组
void BFSTraverse(Graph G){ //对图G进行广度优先遍历
for(i=0;i<G.vexnum;++i)
visited[i]=FALSE; //访问标记数组初始化
InitQueue(Q); //初始化辅助队列Q
for(i=0; i<G.vexnum;++i) //从0号顶点开始遍历
if(!visited[i]) //对每个连通分量调用一次BFS
BFS(G,i); //vi未访问过,从vi开始BFS
}
//广度优先遍历
void BFS(Graph G, int v){ //从顶点v出发,广度优先遍历图G
visit(v); //访问初始化顶点v
visited[v] = TRUE; //对v做已访问标记
Enqueue(Q,v); //顶点v入队列Q
while(!isEmpty(Q)) {
DeQueue(Q,v); //顶点v出队列
for(w=FirstNeighbor(G, v); w>=0;w=NextNeighbor(G, v, w))
//检测v所有邻接点
if(!visited[w]){ //w为v的尚未访问的邻接点
visit(w); //访问顶点w
visited[w] = TRUE; //对w做已访问标记
EnQueue(Q,w); //顶点w入队列
}
}
}
5.3.2 深度优先算法
bool visited[MAX_VERTEX_NUM]; //访问标记数组
void DFSTraverse(Graph G) { //对图G进行深度优先遍历
for(v = 0;v<G.vexnum;++v)
visited[v] = FALSE; //初始化已访问标记数据
for(v = 0;v<G.vexnum;++v) //从v=0开始遍历
if(!visited[v])
DFS(G,v);
}
void DFS(Graph G, int v) { //从顶点v出发,深度优先遍历图G
visit(v); //访问顶点v
visited[v] = TRUE; //设已访问标记
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w))
if(!visited[w]){ //w为u的尚未访问的邻接顶点
DFS(G,w);
}
}
5.4 图的应用
5.4.1 最小生成树
对于一个带权连通无向图,边的权值之和最小的生成树,称为最小生成树。
-
Prim 算法
-
Kruskal 算法
5.4.2 最短路径问题
-
BFS求最短路径问题
void BFS_MIN_Distance(Graph G, int u){ //d[i]表示从u到i结点的最短路径 for(i=0;i<G.vexnum;++i){ d[i] = 0; //初始化路径长度 path[i] = -1; //最短路径从哪个顶点过来 } d[u]=0; visited[u] = TRUE; EnQueue(Q,u); while(!idEmpty(Q)){ //BFS算法主过程 DeQueue(Q,u); //队头元素u出队 for(w = FirstNeighbor(G,u);w>=0;w=NextNeighbor(G, u, w)) if(!visited[w]){ //w为u的尚未访问的邻接顶点 d[w] = d[u]+1; //路径长度加1 path[w] = u; //最短路径应从u到w visited[w] = TRUE; //设已访问标记 EnQueue(Q, w); //顶点w入队 } } }
-
Dijkstra算法----不能解决有负权值的带权图
-
Floyd算法
for(int k=1; k<n; k++) { //考虑以vk作为中转点 for(int i=0; i<n;i++){ //遍历整个矩阵, i为行号,j为列号 if(A[i][j]>A[i][k]+A[k][j]) { //以vk为中转点的路径更短 A[i][j] = A[i][k]+A[k][j]; //更新最短路径长度 path[i][j]=k; //中转点 } } } 注: 可以解决带负权值的图,但是不能解决带有负权回路的图
5.4.3 有向无环图表达式
5.4.4 拓扑排序
bool TopologicalSort(Graph G){
InitStack(S); //初始化栈,存储入度为0的顶点
for(int i = 0; i<G.vexnum;i++)
if(indegree[i]==0)
Push(S,i); //将所有入度为0的顶点入栈
int count = 0; //计数,记录当前已经输出的定点数
while(!IsEmpty(S)) { //栈不空,则存在入度为0的顶点
Pop(S,i); //栈顶点元素出栈
print[count++]=i; //输出顶点i
for(p=G.vertices[i].firstarc;p;p=p->nextarc) { //将所有i指向的顶点的入度减1,并且将入度减为0的顶点压入栈S
v=p->adjvex;
if(!(--indegree[v]))
Push(S,v); //入度为0,则入栈
}
if(count<G.vexnum)
return false; //排序失败,有向图中有回路
else
return true; //拓扑排序成功
}
}
第六章: 查找
6.1 查找的概念
6.2 查找方法
6.2.1 顺序查找
typedef struct{ //查找表的数据结构
ElemType *elem; //动态数组基址
int TableLen; //表的长度
}SSTable;
//顺序查找
int Search_Seq(SSTable ST,ElemType key) {
St.elem[0]=key; //哨兵
int i;
for(i=ST.TableLen;ST.elem[i]!=key;--i); //从后往前找
return i; //查找成功,则返回元素下标;查找失败,则返回0
}
6.2.2 折半查找
int Binary_Search(SSTable L, ElemType key) {
int low=0,high=L.TalbeLen-1,mid;
while(low<=high){
mid=(low+high)/2; //取中间位置
if(L.elem[mid]==key)
return mid; //查找成功则返回所在位置
else if(L.elem[mid]>key)
high=mid-1; //从前半部分继续查找
else
low=mid+1; //查找失败,返回-1
}
return -1; //查找失败,返回-1
}
6.2.3 分块查找
块内无序,块间有序
6.3 B和B+树
6.3.1 B树
1.树的每个结点至多有m棵子树,含有m-1个关键字
2.除根结点外的所有非叶结点至少有[m/2]棵子树,至少含有[m/2]-1个关键字
3.所有的叶结点都出现在同一层次上,并且不带信息
4.对任一结点,其所有子树高度都相同
6.3.2 B树的插入
在插入key后,若导致原结点关键字数超过上限,则从中间位置将其中的关键字分为两部分,左部分包含的关键字放在原结点中,右部分包含的关键字放到新结点中,中间位置的结点插入原结点的父结点
6.3.3 B树的删除
1.若被删除关键字在非终端结点,则用直接前驱或直接后继代替别删除的关键字
直接前驱: 当前关键字左侧指针所指子树中"最右下"的元素
直接后继: 当前关键字右侧指针所指子树中"最左下"的元素
2.若被删除关键字在终端结点
-1.直接删除关键字,若被删除关键字所在结点的个数>=[m/2]
-2.兄弟够借,若被删除关键字的个数为[m/2-1],相邻的兄弟结点关键字的个数>=[m/2],则需要调整该结点左右兄弟结点及双亲结点
-3.若兄弟不够借,被删除的关键字和兄弟结点的个数都为[m/2-1],左右兄弟及双亲结点的关键字进行合并
6.3.4 B+树
1)每个分支结点最多有m棵子树
2)非叶根至少有两棵子树,其他每个分支节点至少有[m/2]棵子树
3)结点的子树个数与关键字个数相等
4)所有叶结点包含全部关键字及指向相应记录的指针,叶结点中将关键字按大小顺序排列,并且相邻叶结点按大小顺序相互链接起来
5)所有分支结点中仅包含它的各个子结点中的关键字的最大值及指向其子结点的指针
注:在B+树中,非叶结点不含有关键字对应记录的存储地址,可以使一个磁盘块包含更多个关键字,使得B+树的阶更大,树高更矮,读磁盘次数更少,查找更快
6.4 散列查找
散列函数: 一个把查找表中的关键字映射成该关键字对应的地址函数
散列函数的构造方法:
1.直接定址法
2.除留余数法
3.数字分析法
4.平方取中法
处理冲突的方法:
1.开放定址法:
-1)线性探测法
-2)平方探测法
-3)在散列法
-4)伪随机法
2.拉链法
第七章: 排序
7.1 排序的基本概念
7.2 插入排序
7.2.1 直接插入排序
void InsertSort(int A[], int n){
int i, j;
for(i=2;i<=n;i++) //依次将A[2]~A[n]插入到前面已排序序列
A[0]=A[i]; //若A[i]关键码小于前驱,将A[i]插入有序表
for(j=i-1;A[0]<A[j];--j) //从后往前查找待插入位置
A[j+1]=A[j]; //向后挪位
A[j+1] = A[0]; //复制到插入位置
}
7.2.2 折半插入排序
void InsertSort(int A[], int n) {
int i, j, low, high, mid;
for(i=2;i<=n;i++) { //依次将A[2]~A[n]插入前面的已排序序列
A[0] = A[i]; //将A[i]暂存到A[0]
low=1;high=i-1; //设置折半查找的范围
while(low<=high){ //折半查找
mid=(low+high)/2; //取中间点
if(A[mid]>A[0]) high=mid-1; //查找左半字表
else low=mid+1; //查找又半子表
}
for(j=i-1;j>=high+1;--j)
A[j+1]=A[j]; //统一后移元素,空出插入位置
A[high+1]=A[0]; //插入操作
}
}
7.2.3 希尔排序
仅适用于顺序表
void ShellSort(int A[], int n){
int d, i, j;
//A[0]只是暂存单元,不是哨兵,当j<=0时,插入位置已到
for(d=n/2; d>=1;d=d/2) //步长变化
for(i=d+1;i<=n;++i)
if(A[i]<A[i-d]){ //需将A[i]插入有序增量子表
A[0]=A[i]; //暂存在A[0]
for(j=i-d;j>0&&A[0]<A[j];j-=d)
A[j+d] = A[j]; //记录后移,查找插入的位置
A[j+d]=A[0]; //插入
}
}
7.3 交换排序
7.3.1 冒泡排序
void BubbleSort(int A[], int n){
for(int i=0;i<n-1;i++){
bool flag=false; //表示本趟冒泡是否发生的标志
for(int j=n-1;j>i;j=-=) //一趟冒泡过程
if(A[j-1]>A[j]){ //若为逆序
swap(A[j-1],A[j]); //交换
flag=true;
}
if(flag==false)
return ; //本趟遍历后没有发生交换,说明表已经有序
}
}
7.3.2 快速排序
//用第一个元素将待排序列划分成左右两个部分
int Partition(int A[], int low,int high){
int pivot = A[low]; //第一个元素作为枢轴
while(low<high) { //用low、hig搜索枢轴的最终位置
while(low<high&&A[hig]>=prvot) --high;
A[low]=A[high]; //比枢轴小的元素移动到左端
while(low<high&&A[low]<=pivot) ++low;
A[high]=A[low]; //比枢轴大的元素移动到右端
}
A[low]=pivot; //枢轴元素存放最终位置
return low; //返回存放枢轴的最终位置
}
//快速排序
void QuickSort(int A[], int low,int high){
if(low<high){ //递归跳出条件
int pivotpos=Partition(A,low,high); //划分
QuickSort(A,low,prvotpos-1); //划分左子表
QucikSort(A,prvotpos+1,high); //划分右子表
}
}
7.4 选择排序
7.4.1 简单选择排序
void SelectSort(int A[], int n){
for(int i = 0;i<n-1;i++){ //一共进行n-1趟
int min=i; //记录最小元素位置
for(int j=i+1;j<n;j++) //在A[i]中选择最小的元素
if(A[j]<A[min]) min=j; //更新最小元素位置
if(min!=i) swap(A[i], A[min]); //封装的swap()函数移动元素3次
}
}
7.4.2 堆排序
void HeadAdjust(int A[], int k, int len){
A[0]=A[k]; //A[0]暂存子树的根结点
for(int i=2*k;i<=len;i*=2){ //沿key较大的子结点向下筛选
if(i<len&&A[i]<A[i+1])
i++; //取key较大的子结点的下标
if(A[0]>=A[i]) break; //筛选结束
else{
A[k]=A[i]; //将A[i]调整到双亲结点上
k=i; //修改k值,以便继续向下筛选
}
}
A[k]=A[0]; //被筛选结点的值放入最终位置
}
7.5 归并排序和基数排序
7.5.1 归并排序
ElemType *B=(ElemType *)malloc((n+1)*sizeof(ElemType)); //辅助数组B
void Merge(ElemType A[], int low,int mid,int high){
//表A的两段A[low~mid]和A[mid+1...high]各自有序,将它们合并成有序表
for(int k=low;k<=high;k++)
B[k]=A[k]; //将A中所有元素复制到B中
for(i=low,j=mid+1,k=i;j<=mid&&j<=high;k++){
if(B[i]<=B[j]) //比较B的左右两段中的元素
A[k]=B[i++]; //将较小值赋值到A中
else
A[k]=B[j++];
}
while(i<=mid) A[k++]=B[i++]; //第一个表未检测完,复制
while(j<=high) A[k++]=B[j++]; //第二个表未检测完,复制
}