目录
逻辑图:
其中:集合结构、树结构、图结构为非线性结构
1.线性表
定义:一个线性表是n个具有相同特性的数据元素的有限序列,线性表的元素仅限于原子项,原子是结构上不可分割的成分
1.1顺序存储结构:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构,通常用数组来描述顺序存储结构,下同
分类:数组
//线性表的顺序存储
#include <stdio.h>
#include <malloc.h> //malloc函数
#include <stdlib.h> //exit函数
typedef struct{
int *elem;//指向数组第一个元素的指针,也就是数组名
int length; //线性表当前长度
int listsize; //线性表最大长度
} SqList;
//初始化
void initSqList(SqList *sq){
int num;
printf("请输入线性表最大长度:");
scanf("%d",&num);
sq->elem=(int *)malloc(num*sizeof(int));//为线性表分配地址
if(!(sq->elem)){
printf("系统分配内存空间失败!\n");
exit(-1);
}
sq->length=0;
sq->listsize=num;
return;
}
//在第i个位置插入数据e
void insertData(SqList *sq,int i,int e){
int j,temp;
if(i>((sq->length)+1)||i<1){
printf("插入失败\n");
exit(-1);
}
for(j=sq->length;j>i-1;j--){
sq->elem[j]=sq->elem[j-1];//插入点之后的元素后移
}
sq->elem[i-1]=e;
(sq->length)++;
}
//遍历
void findAll(SqList L){
for(int i=0;i<L.length;i++){
printf("%d ",L.elem[i]);
}
printf("\n");
}
//查找第i个元素的值
int findById(SqList L,int i){
if(i<1||i>(L.length+1)){
printf("查找失败\n");
exit(-1);
}else{
return L.elem[i-1];
}
}
//删除第i个元素,并返回删除的值
int delete(SqList *L,int i){
int j,temp;
if(i<1||i>(L->length)){
printf("删除失败\n");
exit(-1);
}
temp=L->elem[i-1];
for(j=i-1;j<L->length-1;j++){
L->elem[j]=L->elem[j+1];
}
L->length--;
return temp;
}
//判断表是否为空
bool isEmpty(SqList L){
if(L.length==0){
return true;
}else{
return false;
}
}
//表置空
void clearList(SqList *L){
L->length=0;
return ;
}
//销毁表
void Destory(SqList *L){
free(L);
L->elem=NULL;
L->length=0;
L->listsize=0;
}
int main() {
SqList L;
initSqList(&L);
insertData(&L,1,10);
insertData(&L,2,9);
insertData(&L,3,8);
insertData(&L,4,7);
insertData(&L,5,6);
insertData(&L,5,55);
insertData(&L,2,22);
printf("length:%d\n",L.length);
findAll(L);
printf("%d\n",findById(L,2));
printf("%d\n",delete(&L,2));
findAll(L);
return 0;
}
1.2链式存储结构:结点在存储器中的位置是随意的,即逻辑上相邻的数据元素在物理上不一定相邻(链表)分类:单(向)链表、单向循环链表、双(向)链表、双向循环链表
单链表:
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
typedef struct LNode{
int data;//存放数据
struct LNode *next;//指向下一节点的指针
}LNode,*LinkList;
//单链表初始化,带头节点(头节点不保存数据)
LinkList initList(){
int i;
int length;//单链表有效节点个数
int val;//节点的值
LinkList pHead=(LinkList)malloc(sizeof(LNode));//pHead始终指向头节点
if(pHead==NULL){
printf("分配失败!\n");
exit(-1);
}else{
LinkList pTail=pHead;//pTail始终指向尾接点
pTail->next=NULL;
printf("请输入有效节点个数:");//有效节点个数不包含头节点
scanf("%d",&length);
for(i=0;i<length;i++){
printf("请输入第%d个节点的值:",i+1);
scanf("%d",&val);
LinkList pNew=(LinkList)malloc(sizeof(LNode));
pNew->data=val;
pTail->next=pNew;
pNew->next=NULL;
pTail=pNew;
}
return pHead;
}
}
//求链表的长度
int getLength(LinkList pHead){
int i;
LinkList pTail;
pTail=pHead;
for(i=0;pTail->next!=NULL;i++){
pTail=pTail->next;
}
return i;
}
//判断链表是否为空
bool isEmpty(LinkList pHead){
if(pHead->next==NULL){
return true;
}else{
return false;
}
}
//销毁链表
void destory(LinkList pHead){
LinkList p;
while(pHead!=NULL){
p=pHead->next;
free(pHead);
pHead=p;
}
printf("销毁完毕!\n");
}
//在第i个位置插入元素e
void insertData(LinkList pHead,int i,int e){
int j;
int temp;
LinkList pTail;
LinkList pTemp;
LinkList pt;
pt=(LinkList)malloc(sizeof(LNode));
pt->data=e;
pTail=pHead;
for(j=0;j<i-1;j++){
pTail=pTail->next;
}
pTemp=pTail->next;
pTail->next=pt;
pt->next=pTemp;
}
//删除第i个位置的节点并返回其值
int deleteById(LinkList pHead,int i){
LinkList pTail;
pTail=pHead;
int j;
int data;
LinkList pM;
for(j=0;j<i-1;j++){
pTail=pTail->next;
}
pM=pTail->next->next;
data=pTail->next->data;
pTail->next=pM;
return data;
}
//遍历链表
void findAll(LinkList pHead){
int i;
LinkList pTail;
pTail=pHead;
for(i=0;i<getLength(pHead);i++){
pTail=pTail->next;
printf("第%d个元素的值为%d ",i+1,pTail->data);
}
}
int findById(LinkList pHead,int i){
if(i>getLength(pHead)){
printf("超出范围!\n");
exit(-1);
}else{
int j;
LinkList pTail;
pTail=pHead;
for(j=0;j<i;j++){
pTail=pTail->next;
}
return pTail->data;
}
}
int main() {
LinkList pHead=initList();
findAll(pHead);
printf("\n");
printf("第%d个位置的元素为%d\n",2,findById(pHead,2));
insertData(pHead,2,67);
printf("插入元素后第%d个位置的元素为%d\n",2,findById(pHead,2));
printf("删除第%d个位置元素,其值为:%d\n",3,deleteById(pHead,3));
findAll(pHead);
return 0;
}
单向循环链表:
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
typedef struct LNode{
int data;//存放数据
struct LNode *next;//指向下一节点的指针
}LNode,*LinkList;//结点和头指针
//单向循环链表初始化,带头节点(头节点不保存数据)
LinkList initList(){
int i;
int length;//单链表有效节点个数
int val;//节点的值
LinkList pHead=(LinkList)malloc(sizeof(LNode));//pHead始终指向头节点
if(pHead==NULL){
printf("分配失败!\n");
exit(-1);
}else{
LinkList pTail=pHead;//pTail始终指向尾接点
pTail->next=NULL;
printf("请输入有效节点个数:");//有效节点个数不包含头节点
scanf("%d",&length);
for(i=0;i<length;i++){
printf("请输入第%d个节点的值:",i+1);
scanf("%d",&val);
LinkList pNew=(LinkList)malloc(sizeof(LNode));
pNew->data=val;
pTail->next=pNew;
pNew->next=NULL;
pTail=pNew;
}
pTail->next=pHead;
return pHead;
}
}
void insert(LinkList pHead,int i,int data){
LinkList p,pNew,pTemp;
p=pHead;
pNew=(LinkList)malloc(sizeof(LNode));
pNew->data=data;
int j;
for(j=0;j<i-1;j++){
p=p->next;
}
pTemp=p->next;
p->next=pNew;
pNew->next=pTemp;
}
int deletebyId(LinkList pHead,int i){
LinkList p,pTemp;
int data;
p=pHead;
for(int j=0;j<i-1;j++){
p=p->next;
}
pTemp=p->next;
p->next=pTemp->next;
data=pTemp->data;
free(pTemp);
return data;
}
//遍历链表
void findAll(LinkList pHead){
int i;
LinkList pTail;
pTail=pHead;
for(i=0;pTail->next!=pHead;i++){
pTail=pTail->next;
printf("%d ",pTail->data);
}
printf("\n");
}
int getLength(LinkList pHead){
LinkList p=pHead;
int i;
for(i=0;p->next!=pHead;i++){
p=p->next;
}
return i;
}
void destory(LinkList pHead){
LinkList p=pHead->next,pTemp;
while(p!=pHead){
pTemp=p->next;
printf("%d ",p->data);
free(p);
p=pTemp;
}
free(pHead);
printf("%d ",pHead->data);
}
bool isEmpty(LinkList pHead){
if(pHead->next==pHead){
return true;
}else{
return false;
}
}
void main(){
LinkList pHead=initList();
printf("length:%d\n",getLength(pHead));
findAll(pHead);
insert(pHead,2,50);
printf("length:%d\n",getLength(pHead));
findAll(pHead);
deletebyId(pHead,1);
printf("%s\n",isEmpty(pHead)==1?"是":"否");
findAll(pHead);
destory(pHead);
}
双链表:
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
//定义双链表结点
typedef struct Node{
int data;//数据域
struct Node *prev;//前驱指针
struct Node *next;//后继指针
}Node,*pNode;
//初始化双链表,带头结点
pNode initList(){
pNode p,pNew,pHead;
pHead=(pNode)malloc(sizeof(Node));
if(pHead==NULL){
printf("分配失败!\n");
exit(-1);
}else{
int i,length,val;
pHead->prev=NULL;
pHead->next=NULL;
p=pHead;
printf("请输入有效节点个数:");//有效节点个数不包含头节点
scanf("%d",&length);
for(i=0;i<length;i++){
printf("请输入第%d个节点的值:",i+1);
scanf("%d",&val);
pNew=(pNode)malloc(sizeof(Node));
pNew->data=val;
pNew->next=NULL;
p->next=pNew;
pNew->prev=p;
p=pNew;
}
return pHead;
}
}
//遍历双链表
void findAll(pNode phead){
pNode p;
p=phead;
p=p->next;
while(p!=NULL){
printf("%d ",p->data);
p=p->next;
}
printf("\n");
}
//插入
void insert(pNode pHead,int i,int data){
pNode p,pNew;
pNew=(pNode)malloc(sizeof(Node));
pNew->data=data;
p=pHead;
int j;
for(j=0;j<i-1;j++){
p=p->next;
}
pNew->next=p->next;
p->next->prev=pNew;
p->next=pNew;
pNew->prev=p;
}
//删除
int deleteById(pNode pHead,int i){
pNode p;
p=pHead;
for(int j=0;j<i;j++){
p=p->next;
}
if(p->next!=NULL){
p->prev->next=p->next;
p->next->prev=p->prev;
}else{
p->prev->next=NULL;
}
int data=p->data;
free(p);
return data;
}
//判空
bool isEmpty(pNode pHead){
pNode p=pHead->next;
if(p==NULL){
return true;
}else{
return false;
}
}
//获取长度
int getLength(pNode pHead){
int i;
pNode p=pHead;
for(i=0;p->next!=NULL;i++){
p=p->next;
}
return i;
}
//销毁链表
void destory(pNode pHead){
pNode p;
while(pHead!=NULL){
p=pHead->next;
free(pHead);
pHead=p;
}
}
int main(){
pNode pHead=initList();
printf("链表是否为空?%s\n",isEmpty(pHead)==1?"是":"否");
printf("链表长度为:%d\n",getLength(pHead));
findAll(pHead);
insert(pHead,2,67);
findAll(pHead);
printf("删除结点的值为:%d\n",deleteById(pHead,1));
findAll(pHead);
}
int main(){
pNode pHead=initList();
printf("链表是否为空?%s\n",isEmpty(pHead)==1?"是":"否");
printf("链表长度为:%d\n",getLength(pHead));
findAll(pHead);
printf("删除结点的值为:%d\n",deleteById(pHead,1));
printf("链表是否为空?%s\n",isEmpty(pHead)==1?"是":"否");
printf("链表长度为:%d\n",getLength(pHead));
findAll(pHead);
}
双向循环链表:
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
//定义双链表结点
typedef struct Node{
int data;//数据域
struct Node *prev;//前驱指针
struct Node *next;//后继指针
}Node,*DoubleLinkList;//结点和头指针
//初始化双向循环链表,带头结点
DoubleLinkList initList(){
DoubleLinkList pHead,p,pNew;
pHead=(DoubleLinkList)malloc(sizeof(Node));
if(pHead==NULL){
printf("分配失败!\n");
exit(-1);
}else{
int i,length,val;
p=pHead;
printf("请输入有效节点个数:");//有效节点个数不包含头节点
scanf("%d",&length);
for(i=0;i<length;i++){
printf("请输入第%d个节点的值:",i+1);
scanf("%d",&val);
pNew=(DoubleLinkList)malloc(sizeof(Node));
pNew->data=val;
pNew->prev=p;
pNew->next=pHead;
p->next=pNew;
pHead->prev=pNew;
p=pNew;
}
return pHead;
}
}
void insert(DoubleLinkList pHead,int i,int data){
DoubleLinkList p=pHead,pNew;
pNew=(DoubleLinkList)malloc(sizeof(Node));
pNew->data=data;
for(int j=0;j<i-1;j++){
p=p->next;
}
pNew->next=p->next;
p->next->prev=pNew;
p->next=pNew;
pNew->prev=p;
}
int deletebyId(DoubleLinkList pHead,int i){
DoubleLinkList p=pHead;
for(int j=0;j<i;j++){
p=p->next;
}
p->prev->next=p->next;
p->next->prev=p->prev;
int data=p->data;
free(p);
return data;
}
//遍历双向循环链表
void findAll(DoubleLinkList phead){
DoubleLinkList p;
p=phead;
p=p->next;
while(p!=phead){
printf("%d ",p->data);
p=p->next;
}
printf("\n");
}
int getLength(DoubleLinkList pHead){
DoubleLinkList p=pHead->next;
int i;
for(i=0;p!=pHead;i++){
p=p->next;
}
return i;
}
void destory(DoubleLinkList pHead){
DoubleLinkList p=pHead->next,pTemp;
while(p!=pHead){
pTemp=p->next;
printf("%d ",p->data);
free(p);
p=pTemp;
}
printf("%d ",pHead->data);
free(pHead);
}
bool isEmpty(DoubleLinkList pHead){
if(pHead->next==pHead){
return true;
}else{
return false;
}
}
void main(){
DoubleLinkList pHead=initList();
printf("length:%d\n",getLength(pHead));
findAll(pHead);
insert(pHead,2,65);
findAll(pHead);
printf("%d\n",deletebyId(pHead,3));
findAll(pHead);
printf("%s\n",isEmpty(pHead)==1?"是":"否");
destory(pHead);
}
2. 栈和队列
2.1:栈
定义:先进后出,后进先出,只要满足此条件就可以称为栈,所以我们把数组和链表加上限制条件后便成了栈
主要方法:初始化、压栈、出栈、判空、遍历、清空等
分类:静态栈(顺序存储)、动态栈(链式存储)
应用:数制转换(如:10进制转8进制)、括号匹配检验、行编辑器、迷宫求解、表达式求值、中缀表达式转后缀表达式、递归实现
顺序栈 :将数顺序表(数组)的一端作为栈底,另一端为栈顶
#define INCREMENT 10 //存储空间增量
#include <stdlib.h>
#include <stdbool.h>
typedef struct{
int *base;//栈底指针
int *top;//栈顶指针,栈顶指针指向栈顶元素的上一个位置
int size;//可用容量
}SqStack;
//初始化顺序栈
void initStack(SqStack *s){
int num;
printf("请输入线性表最大长度:");
scanf("%d",&num);
s->base=(int*)malloc(num*sizeof(int));
if(!(s->base)){
printf("系统分配内存空间失败!\n");
exit(-1);
}
s->top=s->base;
s->size=num;
}
//获取栈顶元素
int getTop(SqStack *s){
if(s->top==s->base){
printf("栈空!");
return -1;
}
int *temp;
temp=s->top;
return *(temp-1);//指针的每一次递增/递减,它其实会指向下一个/上一个元素的存储单元
}
//元素入栈
void push(SqStack *s,int data){
if(s->top-s->base>=s->size){
printf("扩容");
s->base=(int*)realloc(s->base,(s->size+INCREMENT)* sizeof(int));
s->top=s->base+s->size;//base的位置上移栈长度得到栈顶元素位置
s->size+=INCREMENT;
}
*s->top=data;
s->top++;
}
//元素出栈
int pop(SqStack *s){
if(s->base==s->top){
printf("栈空!");
return -1;
}
s->top--;
int e=*(s->top);
return e;
}
//获取元素个数
int getLength(SqStack *s){
if(s->base==s->top){
return 0;
}else{
return s->top-s->base;
}
}
//遍历栈
void findAll(SqStack *s){
if(s->top==s->base){
printf("栈空!");
return ;
}
int* temp=s->base;
while(temp<s->top){
printf("%d ",*(temp++));
}
printf("\n");
}
//判空
bool isEmpty(SqStack s){
if(s.base==s.top){
return true;
}else{
return false;
}
}
void clearStack(SqStack *s){
s->top=s->base;
}
void destoryStack(SqStack *s){
s->base=NULL;
s->top=NULL;
s->size=0;
free(s->base);
free(s->top);
printf("销毁成功!");
}
void main(){
SqStack S;
initStack(&S);
printf("请输入有效元素个数:");
int num;
scanf("%d",&num);
for(int i=0;i<num;i++){
int val;
printf("请输入第%d个元素值:",i+1);
scanf("%d",&val);
push(&S,val);
}
printf("遍历结果");
findAll(&S);
printf("%s\n",isEmpty(S)==1?"是":"否");
int length=getLength(&S);
printf("length:%d\n",length);
for(int i=0;i<length;i++){
printf("%d ",pop(&S));
}
printf("\n");
printf("%s\n",isEmpty(S)==1?"是":"否");
destoryStack(&S);
}
void main(){
SqStack S;
initStack(&S);
printf("请输入有效元素个数:");
int num;
scanf("%d",&num);
for(int i=0;i<num;i++){
int val;
printf("请输入第%d个元素值:",i+1);
scanf("%d",&val);
push(&S,val);
}
printf("遍历结果");
findAll(&S);
printf("栈顶元素为:%d\n",getTop(&S));
printf("%s\n",isEmpty(S)==1?"是":"否");
printf("清空栈...\n");
clearStack(&S);
int length=getLength(&S);
printf("length:%d\n",length);
for(int i=0;i<length;i++){
printf("%d ",pop(&S));
}
printf("\n");
printf("%s\n",isEmpty(S)==1?"是":"否");
destoryStack(&S);
}
链栈:将链表的头部作为栈顶,尾部作为栈底
或
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
typedef struct LNode{
int data;
struct linkStack *next;
}LNode,*linkStack;
typedef struct Stack{
linkStack top;//栈顶指针
int size;
}LinkStack;
//初始化空栈,带头结点
LinkStack initLinkStack(LinkStack *S){
S->top=(linkStack)malloc(sizeof(LNode));
if(S->top==NULL){
printf("分配失败!");
exit(-1);
}
S->top->next=NULL;
S->size=0;
}
bool isEmpty(LinkStack *S){
if(S->top->next==NULL){
return true;
}else{
return false;
}
}
int getLength(LinkStack *S){
return S->size;
}
//入栈
void push(LinkStack *S,int data){
printf("元素入栈:%d\n",data);
linkStack pNew=(linkStack)malloc(sizeof(LNode));
pNew->data=data;
pNew->next=S->top;
S->top=pNew;
S->size++;
}
//出栈
int pop(LinkStack *S){
linkStack pNew;
int e=S->top->data;
pNew=S->top;
S->top=pNew->next;
free(pNew);
S->size--;
printf("元素出栈:%d,栈顶元素:%d\n",e,S->top->data);
return e;
}
//获取栈顶元素
int getTop(LinkStack *S){
if(isEmpty(S)){
printf("栈空");
exit(1);
}
return S->top->data;
}
//销毁
void destory(LinkStack *S){
linkStack p;
while(S->top!=NULL){
p=S->top;
S->top=p->next;
printf("%d ",p->data);
free(p);
}
S->size=0;
}
void main(){
LinkStack S;
initLinkStack(&S);
printf("Length:%d\n",getLength(&S));
push(&S,1);
push(&S,2);
push(&S,3);
push(&S,4);
printf("Length:%d\n",getLength(&S));
printf("栈空?%s\n",isEmpty(&S)==1?"是":"否");
printf("栈顶元素为:%d\n",getTop(&S));
int length=getLength(&S);
for(int i=0;i<length;i++){
pop(&S);
}
printf(getLength(&S));
printf("栈空?%s\n",isEmpty(&S)==1?"是":"否");
printf("栈顶元素为:%d",getTop(&S));
]
2.2:队列
定义:先进先出,后进后出,只要满足此条件就可以称为队列
分类:单向对列(按存储结构又可分为顺序队列和链式队列)、双向(端)队列、循环队列(顺序存储,链式没有太大使用意义)
应用:银行排队办理业务、线程池的应用、
线程池工作原理:
- 当有任务提交到线程池,线程池首先会根据核心线程数创建线程来处理这些任务
- 如果核心线程处理不过来,就把任务放到一个阻塞队列等待,当核心线程空闲时从队列把任务取出
- 如果任务再继续来,阻塞队列放不下了,就会创建一些临时线程来处理新加的任务而不是取队列中的任务(临时线程即不超过线程池最大线程数限制的新建线程)
- 如果临时线程也用完了,就开启拒绝策略。
顺序队列
#define MaxSize 20
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
typedef struct Squeue{
int data[MaxSize];
int front;//队头索引
int rear;//队尾索引
}Squeue;
//初始化顺序队列
void initSqueue(Squeue *qu){
qu->front=qu->rear=0;
}
bool isFull(Squeue *qu){
// int length= sizeof(qu->data)/ sizeof(int);
int length=qu->rear-qu->rear;
if(length>=MaxSize){
return true;
}else{
return false;
}
}
bool isEmpty(Squeue *qu){
// int length= sizeof(qu->data)/ sizeof(int);
int length=qu->rear-qu->front;
if(length==0){
return true;
}else{
return false;
}
}
//入队,入队时只有rear在移动
int push(Squeue *qu,int data){
if(isFull(qu)){
printf("队满");
exit(-1);
}
printf("入队:%d\n",data);
qu->data[qu->rear++]=data;
}
int pop(Squeue *qu){
if(isEmpty(qu)){
printf("队空");
exit(-1);
}
int e=qu->data[qu->front];
qu->front++;
return e;
}
int getLength(Squeue *qu){
if(isEmpty(qu)){
return 0;
}
if(isFull(qu)){
return MaxSize;
}
return qu->rear-qu->front;
}
void clearQueue(Squeue *qu){
qu->front=0;
qu->rear=0;
}
void main(){
Squeue qu;
initSqueue(&qu);
push(&qu,1);
push(&qu,2);
push(&qu,3);
push(&qu,4);
clearQueue(&qu);
push(&qu,11);
push(&qu,22);
printf("出队:%d\n",pop(&qu));
printf("Length:%d\n",getLength(&qu));
int length=getLength(&qu);
for(int i=0;i<length;i++){
printf("出队:%d\n",pop(&qu));
}
}
顺序队列经过一系列的入栈出栈操作后, 两个指针最终会到达数组的末端处,虽然队中已没有了元素,如果不进行初始化,仍然无法继续插入元素,这就是所谓的“假溢出”。 为了解决假溢出的问题,可以将数组弄成一个环状,让rear和front指针沿着环走,这样就不会出现无法继续走 下去的情况,这样就产生了顺序循环队列。
为什么0位置不存储元素:为了方便判断队列是队空还是堆满。如果0位置存储元素,则front=rear时,不确定是队空还是队满。而且随着不同情况的入队和出队0的位置也会放入元素,只是始终在队满时rear和front的索引相差1,这样也导致了循环队列能存储的数据个数为数组最大长度-1
#define MaxSize 6
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
typedef struct Squeue{
int data[MaxSize];
int front;//队头索引
int rear;//队尾索引
}Squeue;
//初始化顺序循环队列
void initSqueue(Squeue *qu){
for(int i=0;i<MaxSize;i++){
qu->data[i]=0;
}
qu->front=qu->rear=0;
}
bool isFull(Squeue *qu){
if((qu->rear+1)%MaxSize==qu->front){
return true;
}else{
return false;
}
}
bool isEmpty(Squeue *qu){
if(qu->rear==qu->front){
return true;
}else{
return false;
}
}
//入队,入队时只有rear在移动
int push(Squeue *qu,int data){
if(isFull(qu)){
printf("队满请先将部分元素出队");
exit(-1);
}
printf("入队:%d\n",data);
qu->rear=(qu->rear+1)%MaxSize;
qu->data[qu->rear]=data;
printf("front:%d,rear:%d\n",qu->front,qu->rear);
}
int pop(Squeue *qu){
if(isEmpty(qu)){
printf("队空");
exit(-1);
}
qu->front=(qu->front+1)%MaxSize;
int e=qu->data[qu->front];
qu->data[qu->front]=0;
printf("出队:%d\n",e);
printf("front:%d,rear:%d\n",qu->front,qu->rear);
return e;
}
int getLength(Squeue *qu){
if(isEmpty(qu)){
return 0;
}
if(isFull(qu)){
return MaxSize-1;
}
return (qu->rear + MaxSize - qu->front)%MaxSize;//如果rear<front:rear+maxsize-front;如果rear>front:rear-front;此表达式为俩者的结合写法
}
void findAll(Squeue *qu){
for(int i=0;i<MaxSize;i++){
printf("%d:%d ",i,qu->data[i]);
}
printf("\n");
}
void main(){
Squeue qu;
initSqueue(&qu);
push(&qu,10);
push(&qu,20);
push(&qu,30);
push(&qu,40);
push(&qu,11);
findAll(&qu);
pop(&qu);
pop(&qu);
findAll(&qu);
push(&qu,12);
findAll(&qu);
pop(&qu);
pop(&qu);
push(&qu,66);
findAll(&qu);
int length=getLength(&qu);
for(int i=0;i<length;i++){
pop(&qu);
}
}
链式队列
#include <stdlib.h>
#include <stdbool.h>
typedef struct Node{
int data;
struct Node *next;
}Node,*pNode;
typedef struct Queue{
pNode front;
pNode rear;
}Queue;
void initQueue(Queue *qu){
pNode pHead=(pNode)malloc(sizeof(Node));
if(pHead==NULL){
exit(-1);
}
pHead->next=NULL;
qu->front=qu->rear=pHead;
}
bool isEmpty(Queue *qu){
if(qu->front==qu->rear){
return true;
}else{
return false;
}
}
void push(Queue *qu,int data){
pNode pNew=(pNode)malloc(sizeof(Node));
pNew->data=data;
pNew->next=NULL;
qu->rear->next=pNew;
qu->rear=pNew;
}
int pop(Queue *qu){
if(isEmpty(qu)){
printf("栈空");
exit(-1);
}
pNode p=qu->front->next;
int e=p->data;
qu->front->next=p->next;
free(p);
return e;
}
int getLength(Queue *qu){
int i;
pNode p=qu->front->next;
for(i=0;p!=NULL;i++){
p=p->next;
}
return i;
}
void findAll(Queue *qu){
pNode p=qu->front->next;
while(p!=NULL){
printf("%d ",p->data);
p=p->next;
}
printf("\n");
}
void clearQueue(Queue *qu){
pNode p=qu->front->next,q;
while(p==NULL){
q=p->next;
free(p);
p=q;
}
qu->front=qu->rear;
qu->front->next=NULL;
}
void destory(Queue *qu){
pNode p;
while (qu->front){
p=qu->front->next;
free(qu->front);
qu->front=p;
}
}
void main(){
Queue qu;
initQueue(&qu);
push(&qu,1);
push(&qu,2);
push(&qu,3);
push(&qu,4);
push(&qu,5);
printf("%d\n",getLength(&qu));
findAll(&qu);
int length=getLength(&qu);
for(int i=0;i<length;i++){
printf("出队:%d\n",pop(&qu));
}
}
双端队列:由于应用不多,这里只写原理
类型:一种是数据只能从一段加入而可以从两端取数据 ,另一种是可从两端加入但只从一端取数据
2.3:总结
栈和队列是两种特殊的线性表
3.串
定义:由零个或多个字符组成的有限序列,是数据元素为单个字符的特殊线性表
分类:定长顺序存储串、堆分配存储串(动态分配长度)、块链存储串(即链式存储)
基本操作:赋值操作、连接操作、求串长、窜的比较和求子串
空串和空白串的区别:
空串(Null String)是指长度为零的串
空白串(Blank String)是指包含一个或多个空白字符‘ ’(空格键)的字符串
定长顺序存储串
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#define MAXSIZE 255
//定长顺序存储
typedef struct{
char ch[MAXSIZE];
int length;
}HString;
void initStr(HString *str,char *chars){
int len=strlen(chars);
if(len>MAXSIZE){
printf("字段长度过长!");
exit(-1);
}
for(int i=0;i<len;i++){
str->ch[i]=chars[i];
}
str->length=len;
}
void display(HString *str){
int len=str->length;
for(int i=0;i<len;i++){
printf("%c",str->ch[i]);
}
printf("\n");
}
void main(){
HString str;
char chars[]="abcdefg";
initStr(&str,chars);
display(&str);
}
堆分配存储串
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//堆分配存储串
typedef struct{
char *ch;
int length;
}HString;
//初始化
void initString(HString *str){
str->ch=NULL;
str->length=0;
}
//输出串
void printString(HString *str){
if(str->length==0){
printf("空");
}
printf("长度:%d,",str->length);
for(int i=0;i<str->length;i++){
printf("%c",str->ch[i]);
}
printf("\n");
}
//使用char数组给串赋值
void assign(HString *str,char *chars){
int len=strlen(chars);
str->ch=(char*)malloc(len*sizeof(char));
if(str->ch==NULL){
printf("分配失败");
exit(-1);
}
for(int i=0;i<len;i++){
str->ch[i]=chars[i];
}
str->length=len;
}
void copy(HString *str,HString *newStr){
newStr->ch=(char*)malloc(sizeof(char)*str->length);
for(int i=0;i<str->length;i++){
newStr->ch[i]=str->ch[i];
}
newStr->length=str->length;
}
int compare(HString *str1,HString *str2){
for(int i=0;i<str1->length&&i<str2->length;i++){
if(str1->ch[i]!=str2->ch[i]){//相等的字符跳过,直到不等的字符
return str1->ch[i]-str2->ch[i];
}
}
return str1->length-str2->length;//字符都相等,比较字符串的长度
}
void compareResult(int result){
if(result>0){
printf("前者大于后者\n");
}else if(result<0){
printf("后者大于前者\n");
} else{
printf("相等\n");
}
}
//字符串拼接
void contact(HString *str1,HString *str2,HString *newStr){
int len=(str1->length)+(str2->length);
initString(newStr);
newStr->ch=(char*)malloc(sizeof(char)*len);
for(int i=0;i<str1->length;i++){
newStr->ch[i]=str1->ch[i];
}
for(int i=0;i<str2->length;i++){
newStr->ch[i+str1->length]=str2->ch[i];
}
newStr->length=len;
}
//字符串截取
void substring(HString *str,HString *newStr,int pos,int len){
initString(newStr);
newStr->ch=(char*)malloc(sizeof(char)*len);
for(int i=0;i<len;i++){
newStr->ch[i]=str->ch[i+pos];
}
newStr->length=len;
}
//返回子串在父串中的位置,即子串首字母在母串中的位置
int getPos(HString *parentStr,HString *childStr){
HString *str=(HString*)malloc(sizeof(HString));
initString(str);
int len=childStr->length;
int pos;
for(pos=0;pos+len<parentStr->length;pos++){
substring(parentStr,str,pos,len);
printf("第%d次截取到临时串为:",pos);
printString(str);
if(!compare(str,childStr)){
return pos;
}
}
return 0;
}
//删除指定个字符
void delete(HString *str,int pos,int len){
for(int i=pos;i<str->length-len;i++){
str->ch[i]=str->ch[i+len];
}
str->length=str->length-len;
}
//插入字符
void insert(HString *str,int pos,HString *insertStr){
int len=str->length+insertStr->length;
str->ch=(char*)realloc(str->ch, sizeof(char)*len);//重新调整str的容量
for(int i=len-1;i>=pos+insertStr->length-1;i--){
str->ch[i]=str->ch[i-(insertStr->length)];
}
for(int i=0;i<insertStr->length;i++){
str->ch[i+pos]=insertStr->ch[i];
}
str->length=len;
}
//将str中的oldstr替换成newstr
void replace(HString *oldstr,HString *newstr,HString *str){
int pos=getPos(str,oldstr);
delete(str,pos,oldstr->length);
insert(str,pos,newstr);
}
void main(){
HString str;
initString(&str);
char chars[]="abcdefg";
assign(&str,chars);
printString(&str);
HString newStr;
initString(&newStr);
copy(&str,&newStr);
printString(&newStr);
char newchars1[]="def";
HString newStr2;
assign(&newStr2,newchars1);
compareResult(compare(&str,&newStr));
compareResult(compare(&str,&newStr2));
HString str3;
contact(&str,&newStr,&str3);
printString(&str3);
HString str4;
substring(&str,&str4,2,3);
printString(&str4);
printf("============================\n");
printString(&str3);
printString(&str4);
printf("位置:%d",getPos(&str3,&str4));
printf("\n");
delete(&str,2,3);
printf("str:");
printString(&str);
printf("插入后:");
insert(&str,2,&str4);
printString(&str);
HString str5;
initString(&str5);
char chars2[]="www";
assign(&str5,chars2);
replace(&str4,&str5,&str);
printf("str替换后为:");
printString(&str);
}
块链存储(使用链表来存储,每个结点存储一个字符或多个字符):
//块链存储(使用链表来存储,每个结点存储一个字符或多个字符)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define linkNum 3 //单个节点可存储数据个数
typedef struct Node{
char arr[linkNum];
struct Node *next;
}Node,*pNode;
pNode initLinkString(char *chars){
int len=strlen(chars);
int nodeNum=len/linkNum;//所需结点个数
if(len%nodeNum!=0){
nodeNum++;
}
pNode pHead=(pNode)malloc(sizeof(Node));
pHead->next=NULL;
pNode p=pHead;
for(int i=0;i<nodeNum;i++){
int j=0;
for(;j<linkNum;j++){
if(i*linkNum+j<len){
p->arr[j]=chars[i*linkNum+j];
}else{
p->arr[j]='#';
}
}
if(i*linkNum+j<len){
pNode pNew=(pNode)malloc(sizeof(Node));
pNew->next=NULL;
p->next=pNew;
p=pNew;
}
}
return pHead;
}
void displayLinkString(pNode pHead){
pNode p=pHead;
while(p!=NULL){
for(int i=0;i<linkNum;i++){
printf("%c",p->arr[i]);
}
p=p->next;
}
}
void main(){
char chars[]="helloworld!";
pNode pHead=initLinkString(chars);
displayLinkString(pHead);
}
4.数组和广义表
参考:http://data.biancheng.net/view/186.html
4.1:数组
定义:用一组连续的内存空间来存储一组具有相同类型的数据
分类:一维数组、二维数组、多维数组
应用:对称矩阵的压缩存储、上(下)三角矩阵的压缩存储、稀疏矩阵(矩阵中包含大量0元素)的压缩存储
稀疏矩阵存储——三元组顺序表压缩存储,三元组顺序表每次提取指定元素都需要遍历整个数组,运行效率很低
#define number 20
typedef struct {
int i,j;//行索引,列索引
int data;
}Node;
typedef struct {
Node data[number];//存储所有非0元素
int lines,lists,num;//行数、列数、非0元素个数
}TSMatrix;
void display(TSMatrix M){
for(int i=1;i<=M.lines;i++){
for(int j=1;j<=M.lists;j++){
int value=0;
for(int k=0;k<M.num;k++){
if(i==M.data[k].i&&j==M.data[k].j){
printf("%d ",M.data[k].data);
value=1;
break;
}
}
if(value==0){
printf("0 ");
}
}
printf("\n");
}
}
void main(){
TSMatrix M;
M.lines=3;
M.lists=3;
M.num=3;
M.data[0].i=1;
M.data[0].j=1;
M.data[0].data=1;
M.data[1].i=2;
M.data[1].j=3;
M.data[1].data=5;
M.data[2].i=3;
M.data[2].j=1;
M.data[2].data=3;
display(M);
}
行逻辑链接顺序存储:
#include <stdio.h>
#define MAXSIZE 12500
#define MAXRC 100
typedef struct {
int i,j;
int data;
}Node;//三元组结点
//行逻辑链接顺序存储
typedef struct {
Node data[MAXSIZE+1];
int rpos[MAXRC+1];
int lines,lists,num;
}RLSMatrix;
void display(RLSMatrix M){
for(int i=1;i<=M.lines;i++){
for(int j=1;j<=M.lists;j++){
int value=0;
if(i+1 <=M.lines){
for(int k=M.rpos[i];k<M.rpos[i+1];k++){
if(i == M.data[k].i && j == M.data[k].j){
printf("%d ",M.data[k].data);
value=1;
break;
}
}
if(value==0){
printf("0 ");
}
}else{
for(int k=M.rpos[i];k<=M.num;k++){
if(i == M.data[k].i && j == M.data[k].j){
printf("%d ",M.data[k].data);
value=1;
break;
}
}
if(value==0){
printf("0 ");
}
}
}
printf("\n");
}
}
void main(){
/*0 3 0 5*/
/*0 0 1 0*/
/*2 0 0 0*/
//(1,2,3)|(1,4,5)|(2,3,1)|(3,1,2)
RLSMatrix M;
M.num = 4;
M.lines = 3;
M.lists = 4;
M.rpos[1] = 1;//第一行第一个非0元素在一维数组的第1个位置
M.rpos[2] = 3;//第二行第一个非0元素在一维数组的第3个位置
M.rpos[3] = 4;//第三行第一个非0元素在一维数组的第4个位置
M.data[1].data = 3;
M.data[1].i = 1;
M.data[1].j = 2;
M.data[2].data = 5;
M.data[2].i = 1;
M.data[2].j = 4;
M.data[3].data = 1;
M.data[3].i = 2;
M.data[3].j = 3;
M.data[4].data = 2;
M.data[4].i = 3;
M.data[4].j = 1;
//输出矩阵
display(M);
}
十字链表法存储:
数组 "不利于插入和删除数据" 的特点,以上两种压缩存储方式都不适合解决类似 "向矩阵中添加或删除非 0 元素" 的问题
#include <stdlib.h>
#include <stdio.h>
typedef struct OLNode{
int i,j,data;//三元组
struct OLNode *right,*down;//右指针、下指针(右指针指向同行结点,下指针指向下一行结点)
}OLNode,*OLink;
//十字链表法存储
typedef struct {
OLink *rhead,*chead;//行链表数组,列链表数组
int rows,cols,nums;//行数、列数、非零元素个数
}CrossList;
CrossList createMatrix(CrossList M){
int rows,cols,nums;
printf("输入行数、列数、非0元素个数:");
scanf("%d%d%d",&rows,&cols,&nums);
M.rows=rows;
M.cols=cols;
M.nums=nums;
M.rhead=(OLink*)malloc(sizeof(OLNode)*(rows+1));
M.chead=(OLink*)malloc(sizeof(OLNode)*(cols+1));
if(M.rhead==NULL||M.chead==NULL){
printf("初始化失败!");
exit(-1);
}
int i;
for(i=1;i<=rows;i++){
M.rhead[i]=NULL;
}//把行数组的各项都赋值为空
int j;
for(j=1;j<=cols;j++){
M.chead[j]=NULL;
}//把列数组的各项都赋值为空
int data;
OLink p,q;
while (i!=0){
printf("请输入一个结点的三元信息(行号、列号、值):");
scanf("%d%d%d",&i,&j,&data);
p=(OLNode*)malloc(sizeof(OLNode));
if(p==NULL){
printf("初始化三元组失败");
exit(-1);
}
p->i=i;
p->j=j;
p->data=data;
//找到行的位置
if(M.rhead[i]==NULL||M.rhead[i]->j>j){
p->right=M.rhead[i];
M.rhead[i]=p;
}else{
for(q=M.rhead[i];(q->right)&&q->right->j<j;q=q->right);
p->right=q->right;
q->right=p;
}
//找到列的位置
if(M.chead[j]==NULL||M.chead[j]->i>i){
p->down=M.chead[j];
M.chead[j]=p;
}else{
for(q=M.chead[j];(q->down)&&q->down->i<i;q=q->down);
p->down=q->down;
q->down=p;
}
}
return M;
}
void display(CrossList M){
for (int i = 1; i <= M.cols; i++)
{
if (NULL != M.chead[i])
{
OLink p = M.chead[i];
while (NULL != p)
{
printf("%d %d %d\n", p->i, p->j, p->data);
p = p->down;
}
}
}
}
void main(){
CrossList M;
M.rhead=NULL;
M.chead=NULL;
M=createMatrix(M);
display(M);
}
逻辑结构图
4.2:广义表
参考:http://data.biancheng.net/view/190.html
为什么会出现广义表?因为数组只能存储同种类型的元素,如 {1,2,3} | { {1,2,3},{4,5,6}} 。
但如果我们想存储这样的数据,如 {1,{1,2,3}} 则不行了,因此我们用广义表来存储
定义:n个元素的序列,广义表的元素或者是原子项,或者是一个广义表
LS = (a1,a2,…,an),LS 代表广义表的名称,ai表示广义表存储的数据, ai 既可以代表单个元素(原子),也可以代表另一个广义表(子表)
存储:链式存储(如果使用顺序存储则需要操作n为数组(如: {1,{2,{3,4}}}要使用三维数组)造成存储空间的浪费)
广义表节点结构:
tag 标记位用于区分此节点是原子还是子表,通常原子的 tag 值为 0,子表的 tag 值为 1
hp 指针用于连接本子表中存储的原子或子表
tp 指针用于连接广义表中下一个原子或子表
#include <stdlib.h>
typedef struct GLNode{
int tag;
union{
char atom;
struct {
struct GLNode *hp,*next;//hp指向指向原子节点,next指向下一个节点
}ptr;
};
}*Glist;
//{a,{b,c,d}}
Glist createGlist(Glist C){
//头指针
C=(Glist)malloc(sizeof(Glist));
//定义原子节点
C->tag=1;
C->ptr.hp=(Glist)malloc(sizeof(Glist));
C->ptr.hp->tag=0;
C->ptr.hp->atom='a';
C->ptr.next=(Glist)malloc(sizeof(Glist));
C->ptr.next->tag=1;
C->ptr.next->ptr.hp=(Glist)malloc(sizeof(Glist));
C->ptr.next->ptr.next=NULL;
C->ptr.next->ptr.hp->tag=1;
C->ptr.next->ptr.hp->ptr.hp=(Glist)malloc(sizeof(Glist));
C->ptr.next->ptr.hp->ptr.hp->tag=0;
C->ptr.next->ptr.hp->ptr.hp->atom='b';
C->ptr.next->ptr.hp->ptr.next=(Glist)malloc(sizeof(Glist));
C->ptr.next->ptr.hp->ptr.next->tag=1;
C->ptr.next->ptr.hp->ptr.next->ptr.hp=(Glist)malloc(sizeof(Glist));
C->ptr.next->ptr.hp->ptr.next->ptr.hp->tag=0;
C->ptr.next->ptr.hp->ptr.next->ptr.hp->atom='c';
C->ptr.next->ptr.hp->ptr.next->ptr.next=(Glist)malloc(sizeof(Glist));
C->ptr.next->ptr.hp->ptr.next->ptr.next->tag=1;
C->ptr.next->ptr.hp->ptr.next->ptr.next->ptr.hp=(Glist)malloc(sizeof(Glist));
C->ptr.next->ptr.hp->ptr.next->ptr.next->ptr.hp->tag=0;
C->ptr.next->ptr.hp->ptr.next->ptr.next->ptr.hp->atom='d';
C->ptr.next->ptr.hp->ptr.next->ptr.next->ptr.next=NULL;
}
另一种节点结构:
tag 标记位用于区分此节点是原子还是子表,通常原子的 tag 值为 0,子表的 tag 值为 1
action:标识原子值
hp 指针用于连接本子表中存储的原子或子表
tp 指针用于连接广义表中下一个原子或子表
#include <stdlib.h>
//这种存储方式更简单一些
typedef struct GLNode{
int tag;
union{
int atom;
struct GLNode *hp;
};
struct GLNode *tp;
}*Glist;
Glist creatGlist(Glist C){
C=(Glist)malloc(sizeof(Glist));
C->tag=1;
C->hp=(Glist)malloc(sizeof(Glist));
C->tp=NULL;
//表头原子a
C->hp->tag=0;
C->atom='a';
C->hp->tp=(Glist)malloc(sizeof(Glist));
C->hp->tp->tag=1;
C->hp->tp->hp=(Glist)malloc(sizeof(Glist));
C->hp->tp->tp=NULL;
//原子b
C->hp->tp->hp->tag=0;
C->hp->tp->hp->atom='b';
C->hp->tp->hp->tp=(Glist)malloc(sizeof(Glist));
//原子c
C->hp->tp->hp->tp->tag=0;
C->hp->tp->hp->tp->atom='c';
C->hp->tp->hp->tp->tp=(Glist)malloc(sizeof(Glist));
//原子d
C->hp->tp->hp->tp->tp->tag=0;
C->hp->tp->hp->tp->tp->atom='d';
C->hp->tp->hp->tp->tp->tp=NULL;
return C;
}
广义表深度和长度的计算:
广义表的长度: 若广义表不空,则广义表所包含的元素的个数,叫广义表的长度。
广义表的深度: 广义表中括号的最大层数叫广义表的深度。
举例:LS=((),a,b,(a,b,c),(a,(a,b),c)),
其中表头为子表LSH = (),表尾为子表LST = (a,b,(a,b,c),(a,(a,b),c))
广义表LS的长度:5,其中5哥元素分别为:()、a、b、(a,b,c)、(a,(a,b),c),所以长度为5
广义表LS的深度:3,其中括号的层数依次是2,1,1,2,3,所以其最大层数为3,即深度为3
5.树和二叉树
引用:http://data.biancheng.net/view/193.html
5.1:树
定义:它是由n(n>=1)个有限结点组成一个具有层次关系的集合
相关概念:根结点和叶子结点、子树和空树、结点的度(结点有多少分支)和层(根节点为第一层依此类推到叶子结点)、树的度(树内结点度的最大值)和层、有序树(规定结点从左到右看)和无序树(没有规定)
存储方式:双亲表示法、孩子表示法、孩子兄弟表示法
操作:树转二叉树、遍历(先根遍历、后根遍历)
双亲表示法:采用顺序表存储
#define MAX_SIZE 100//宏定义树中结点的最大数量
#include<stdio.h>
#include<stdlib.h>
typedef struct Snode{
int data;//树中结点的数据类型
int parent;//结点的父结点在数组中的位置下标
}PTNode;
typedef struct {
PTNode tnode[MAX_SIZE];//存放树中所有结点
int n;//根的位置下标和结点数
}PTree;
PTree InitPNode(PTree tree)
{
int i,j;
char ch;
printf("请输出节点个数:\n");
scanf("%d",&(tree.n));
printf("请输入结点的值其双亲位于数组中的位置下标:\n");
for(i=0; i<tree.n; i++)
{
fflush(stdin);
scanf("%c %d",&ch,&j);
tree.tnode[i].data = ch;
tree.tnode[i].parent = j;
}
return tree;
}
void FindParent(PTree tree)
{
char a;
int isfind = 0;
printf("请输入要查询的结点值:\n");
fflush(stdin);
scanf("%c",&a);
for(int i =0;i<tree.n;i++){
if(tree.tnode[i].data == a){
isfind=1;
int ad=tree.tnode[i].parent;
printf("%c的父节点为 %c,存储位置下标为 %d",a,tree.tnode[ad].data,ad);
break;
}
}
if(isfind == 0){
printf("树中无此节点");
}
}
void main(){
PTree tree;
tree = InitPNode(tree);
FindParent(tree);
}
孩子表示法:采用的是 "顺序表+链表" 的组合结构
#include <stdio.h>
#include <stdlib.h>
//定义孩子链表的每个结点
typedef struct CTNode{
int child;//孩子结点的下标
struct CTNode *next;//指向孩子结点的指针
}ChildPtr;
typedef struct {
int data;//头节点保存的值
ChildPtr *firstchild;//孩子链表的头指针
}CTBox;
typedef struct {
CTBox nodes[20];//保存头节点的数组
int n,r;//n代表结点数量,r代表根的位置
}CTree;
CTree initTree(CTree tree){
printf("输入节点数量:\n");
scanf("%d",&(tree.n));
for(int i=0;i<tree.n;i++){
printf("输入第 %d 个节点的值:\n",i+1);
fflush(stdin);
scanf("%c",&(tree.nodes[i].data));
tree.nodes[i].firstchild=(ChildPtr*)malloc(sizeof(ChildPtr));
tree.nodes[i].firstchild->next=NULL;
printf("输入节点 %c 的孩子节点数量:\n",tree.nodes[i].data);
int Num;
scanf("%d",&Num);
if(Num!=0){
ChildPtr *p = tree.nodes[i].firstchild;
for(int j = 0 ;j<Num;j++){
ChildPtr * newEle=(ChildPtr*)malloc(sizeof(ChildPtr));
newEle->next=NULL;
printf("输入第 %d 个孩子节点在顺序表中的位置",j+1);
scanf("%d",&(newEle->child));
p->next= newEle;
p=p->next;
}
}
}
return tree;
}
void findKids(CTree tree,char a){
int hasKids=0;
for(int i=0;i<tree.n;i++){
if(tree.nodes[i].data==a){
ChildPtr * p=tree.nodes[i].firstchild->next;
while(p){
hasKids = 1;
printf("%c ",tree.nodes[p->child].data);
p=p->next;
}
break;
}
}
if(hasKids==0){
printf("此节点为叶子节点");
}
}
void main(){
CTree tree;
tree = initTree(tree);
//默认数根节点位于数组notes[0]处
tree.r=0;
printf("找出节点 F 的所有孩子节点:");
findKids(tree,'F');
}
孩子兄弟表示法:从树的根节点开始,依次用链表存储各个节点的孩子节点和兄弟节点,通过孩子兄弟表示法,任意一棵普通树都可以相应转化为一棵二叉树,即:任意一棵普通树都有唯一的一棵二叉树于其对应
B-树:
B+树:
键树:
5.2:二叉树
定义:树中各结点的度不超过2的有序树
性质:第i层右2^(i-1)个结点;最多有2^K-1个节点(K为树的深度,等于树的层);叶子结点数=度为2的结点数+1
操作:创建节点、插入节点、查找节点、删除节点、遍历二叉树(先序、中序、后序)、销毁二叉树、求节点或叶子节点个数、求树高、求路径、交换节点的左右孩子
分类:完全二叉树、满二叉树(除了叶子结点每个结点的度都为2)、哈夫曼树(带权路径长度最小的二叉树又叫最优二叉树)、平衡二叉树(又叫AVL树、任意节点的子树的高度差都小于等于1)、线索二叉树(加上结点前趋后继信息的二叉树)、二叉排序树(又叫二叉查找树)、红黑树(解决二叉排序树插入新节点导致的不平衡)、B-树(B树)、B+树(对B-树的简化)、
存储结构:顺序存储结构(仅仅适用于满或完全二叉树,普通二叉树想顺序存储需要补成完全二叉树才可)、二叉链表法(每个节点存储左子树和右子树)/三叉链表法(左子树、右子树、父节点)、
完全二叉树的顺序存储:
顺序表还原完全二叉树:根据第i结点,其左孩子是2^i,右孩子是2^i+1来构建。
二叉树链式存储:因为普通二叉树使用顺序存储不是很遍历,所以我们提出了链式存储
其中Lchild:指向左孩子结点的指针、data:保存数据,Rchild指向右孩子结点的指针
#include <stdlib.h>
//定义二叉数结点
typedef struct BiTNode{
int data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
BiTree createBiTree(){
BiTree T=(BiTree)malloc(sizeof(BiTree));
T->data=1;
T->lchild=(BiTNode*)malloc(sizeof(BiTNode));
T->lchild->data=2;
T->rchild=(BiTNode*)malloc(sizeof(BiTNode));
T->rchild->data=3;
T->rchild->lchild=NULL;
T->rchild->rchild=NULL;
T->lchild->lchild=(BiTNode*)malloc(sizeof(BiTNode));
T->lchild->lchild->data=4;
T->lchild->rchild=NULL;
T->lchild->lchild->lchild=NULL;
T->lchild->lchild->rchild=NULL;
return T;
}
void main(){
BiTree T=createBiTree();
printf("%d",T->lchild->lchild->data);
}
三叉链表: 方便查找某节点的父节点
二叉树的遍历:先序遍历(根-左-右)、中序遍历(左-根-右)、后序遍历(左-右-根)
#include <stdlib.h>
#include <stdio.h>
typedef struct BiTNode{
int data;
struct BiTNode *left,*right;
}BiTNode;
//先序
void preOrder(BiTNode *node){
if(node!=NULL){
printf("%d ",node->data);
preOrder(node->left);
preOrder(node->right);
}
}
//中序
void midOrder(BiTNode *node){
if(node!=NULL){
midOrder(node->left);
printf("%d ",node->data);
midOrder(node->right);
}
}
//后序
void endOrder(BiTNode *node){
if(node!=NULL){
endOrder(node->left);
endOrder(node->right);
printf("%d ",node->data);
}
}
void main(){
BiTNode n1;
BiTNode n2;
BiTNode n3;
BiTNode n4;
n1.data=1;
n2.data=2;
n3.data=3;
n4.data=4;
n1.left=&n2;
n1.right=&n3;
n2.left=&n4;
n2.right=NULL;
n3.left=NULL;
n3.right=NULL;
n4.left=NULL;
n4.right=NULL;
printf("先序遍历的结果为:");
preOrder(&n1);
putchar('\n');
printf("中序遍历的结果为:");
midOrder(&n1);
putchar('\n');
printf("后序遍历的结果为:");
endOrder(&n1);
putchar('\n');
}
线索二叉树:
我们发现使用链式存储时,结点会有许多空指针,这样不利于资源的有效利用,所以我们提出了线索二叉树的概念:
当 ltag = 0 时, lchild 指向结点的左孩子;
当 ltag = 1 时, lchild 指向结点的直接前驱;
当 rtag = 0 时, rchild 指向结点的右孩子;
当 rtag = 1 时, rchild 指向结点的直接后继
平衡二叉树:
二叉排序树:
定义:
- 二叉排序树中,如果其根结点有左子树,那么左子树上所有结点的值都小于根结点的值;
- 二叉排序树中,如果其根结点有右子树,那么右子树上所有结点的值都大小根结点的值;
- 二叉排序树的左右子树也要求都是二叉排序树;
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct BiNode{
int data;
struct BiNode *left;
struct BiNode *right;
}BiNode,*BiTree;
//使用二叉排序树查找关键字
//BiTree SearchBST(BiTree T,int key){
// if(!T&&key==T->data){
// return T;
// }else if(key<T->data){
// SearchBST(T->left,key);
// }else{
// SearchBST(T->right,key);
// }
//}
//使用二叉排序树查找关键字,若不存在则用指针保存失败位置
bool SearchBST(BiTree T,int key,BiTree f,BiTree *p){
if(!T){
*p=f;//令 p 指针指向查找过程中最后一个叶子结点
return false;
}else if(key==T->data){
*p=T;//令 p 指针指向该关键字
return true;
}else if(key<T->data){
return SearchBST(T->left,key,T,p);
}else{
return SearchBST(T->right,key,T,p);
}
}
//二叉排序树中插入关键字
bool insertBST(BiTree *T,int e){
BiTree p=NULL;
if(!SearchBST((*T),e,NULL,&p)){
//如果查找不成功
BiTree s=(BiTree)malloc(sizeof(BiTree));
s->data=e;
s->left=s->right=NULL;
if(!p){//如果 p 为NULL,说明该二叉排序树为空树,此时插入的结点为整棵树的根结点
*T=s;
}else if(e<p->data){//如果 p 不为 NULL,则 p 指向的为查找失败的最后一个叶子结点,只需要通过比较 p 和 e 的值确定 s 到底是 p 的左孩子还是右孩子
p->left=s;
}else{
p->right=s;
}
return true;
}
return false;
}
int delete(BiTree *p){
BiTree q,s;
if(!(*p)->left && !(*p)->right){//待删除结点左右子树均为空
*p=NULL;
}else if(!(*p)->left){//左子树为空
q=*p;
*p=(*p)->right;
free(q);
}else if(!(*p)->right){//右子树为空
q=*p;
*p=(*p)->left;
free(q);
}else{//左右子树均存在
q=*p;
s=(*p)->left;
while(s->right){//找到p的直接前驱
q=s;
s=s->right;
}
(*p)->data=s->data;
//判断结点s 是否有左子树,分为两种情况讨论
if(q!=*p){
q->right=s->left;//若有,令s的左孩子结点改为 q 的孩子结点
}else{
q->left=s->left;//否则,直接将s左子树上移即可
}
free(s);
}
return true;
}
int DeleteBST(BiTree *T,int key){
if(!(*T)){//关键字不存在
return false;
}else{
if(key==(*T)->data){
delete(T);
return true;
}else if(key<(*T)->data){
return DeleteBST(&(*T)->left,key);
}else{
return DeleteBST(&(*T)->right,key);
}
}
}
//中序遍历
void order(BiTree t){
if(t==NULL){
return;
}
order(t->left);
printf("%d ",t->data);
order(t->right);
}
void main(){
int i;
int a[5]={3,4,2,5,9};
BiTree T=NULL;
for(i=0;i<5;i++){
insertBST(&T,a[i]);
}
printf("中序遍历:");
order(T);
printf("\n");
printf("删除3后,中序遍历二叉排序树:\n");
DeleteBST(&T,3);
order(T);
}
使用二叉排序树表示的动态查找表中删除某个数据元素时,需要在成功删除该结点的同时,依旧使这棵树为二叉排序树,假设要删除的为结点 p,则对于二叉排序树来说,需要根据结点 p 所在不同的位置作不同的操作,有以下 3 种可能:
- 结点 p 为叶子结点,此时只需要删除该结点,并修改其双亲结点的指针即可;
- 结点 p 只有左子树或者只有右子树,此时只需要将其左子树或者右子树直接变为结点 p 双亲结点的左子树或右子树即可;
- 结点 p 左右子树都有,此时有两种处理方式:1)令结点 p 的左子树为其双亲结点的左子树;结点 p 的右子树为其自身直接前驱结点的右子树;2)用结点 p 的直接前驱(或直接后继)来代替结点 p,同时在二叉排序树中对其直接前驱(或直接后继)做删除操作
使用二叉排序树查找关键字:
首先将被查找值同树的根结点进行比较,会有 3 种不同的结果:
- 如果相等,查找成功;
- 如果比较结果为根结点的关键字值较大,则说明该关键字可能存在其左子树中;
- 如果比较结果为根结点的关键字值较小,则说明该关键字可能存在其右子树中;
查找:
使用二叉排序树在查找表中做查找操作的时间复杂度同建立的二叉树本身的结构有关,二叉排序树构造会影响查找算法的效率,需要对二叉排序树做“平衡化”处理
平衡二叉树:
定义:平衡二叉树,又称为 AVL 树。实际上就是遵循以下两个特点的二叉树:
- 每棵子树中的左子树和右子树的深度差不能超过 1;
- 二叉树中每棵子树都要求是平衡二叉树
平衡因子:每个结点的就是其左子树深度同右子树深度的差,平衡因子的取值只可能是:0、1 和 -1
把二叉排序树转换成平衡二叉树:
为了排除动态查找表中不同的数据排列方式对算法性能的影响,需要考虑在不会破坏二叉排序树本身结构的前提下,将二叉排序树转化为平衡二叉树。
我们以查找表{13,24,37,90,53}
构建二叉排序树为例
二叉树旋转操作:
单向右旋平衡处理:左子树插入导致失去平衡
单向左旋平衡处理:右子树插入导致失去平衡
双向旋转(先左后右)平衡处理:
左旋转:
右旋转:
双向旋转(先右后左)平衡处理:
哈夫曼树:
当用 n 个结点(都做叶子结点且都有各自的权值)试图构建一棵树时,如果构建的这棵树的带权路径长度最小,称这棵树为“最优二叉树”或者“哈夫曼树”。要使树的带权路径长度最小,则权重越大的结点离树根越近
路径:在一棵树中,一个结点到另一个结点之间的通路,称为路径。
路径长度:在一条路径中,每经过一个结点,路径长度都要加 1
结点的权:给每一个结点赋予一个新的数值,被称为这个结点的权,图 1 中结点 a 的权为 7,结点 b 的权为 5
结点的带权路径长度:指的是从根结点到该结点之间的路径长度与该结点的权的乘积,图 1 中结点 b 的带权路径长度为 2 * 5 = 10
树的带权路径长度为树中所有叶子结点的带权路径长度之和。通常记作 “WPL”,图 1 中所示的这颗树的带权路径长度为:WPL = 7 * 1 + 5 * 2 + 2 * 3 + 4 * 3
图 1 中从根结点到结点 c 的路径长度为 3
构建哈夫曼树的方法:
- 在 n 个权值中选出两个最小权值对应的两个结点组成一个新的二叉树,且新二叉树的根结点的权值为左右孩子权值的和;
- 在原有的 n 个权值中删除那两个最小的权值,同时将新的权值加入到 n–2 个权值的行列中,以此类推;
- 重复 1 和 2 ,直到所以的结点构建成了一棵二叉树为止,这棵树就是哈夫曼树。
哈夫曼编码:
哈夫曼编码就是在哈夫曼树的基础上构建的,哈夫曼树中的每一个子树,统一规定其左孩子标记为 0 ,右孩子标记为 1
字符 a 在哈夫曼编码是 0
,字符 b 编码为 10
,字符 c 的编码为 110
,字符 d 的编码为 111
使用程序求哈夫曼编码有两种方法:
- 从叶子结点一直找到根结点,逆向记录途中经过的标记。例如,图 3 中字符 c 的哈夫曼编码从结点 c 开始一直找到根结点,结果为:0 1 1 ,所以字符 c 的哈夫曼编码为:1 1 0(逆序输出)。
- 从根结点出发,一直到叶子结点,记录途中经过的标记。例如,求图 3 中字符 c 的哈夫曼编码,就从根结点开始,依次为:1 1 0
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
typedef struct {
int weight;//结点权重
int parent,left,right;//父节点、左孩子、右孩子在数组中的下标
}HTNode,*HuffmanTree;
typedef char **HuffmanCode;//存储哈夫曼编码
//需要每次根据各个结点的权重值,筛选出其中值最小的两个结点,end表示HT数组中存放结点的最终位置,s1和s2传递的是HT数组中权重值最小的两个结点在数组中的位置
void select(HuffmanTree HT,int end,int *s1,int *s2){
int min1,min2;
int i=1;
while(HT[i].parent!=0&&i<=end){
i++;
}
min1=HT[i].weight;
*s1=i;
i++;
while(HT[i].parent!=0&&i<=end){
i++;
}
//俩个结点比较大小,min1为较小的
if(HT[i].weight<min1){
min2=min1;
*s2=*s1;
min1=HT[i].weight;
*s1=i;
}else{
min2=HT[i].weight;
*s2=i;
}
//俩个结点和后续的所有未构成树的结点比较
for(int j=i+1;j<=end;j++){
if(HT[j].parent!=0){
continue;
}
if(HT[j].weight<min1){
min2=min1;
min1=HT[j].weight;
*s2=*s1;
*s1=j;
}else if(HT[j].weight>=min1&&HT[j].weight<min2){
min2=HT[j].weight;
*s2=j;
}
}
}
//HT为存储哈夫曼树的数组,w为存储结点权值的数组,n为结点个数
void createHuffmanTree(HuffmanTree *HT,int *w,int n){
if(n<=1) return;
int m=2*n-1;
*HT=(HuffmanTree)malloc((m+1)* sizeof(HTNode));
HuffmanTree p= *HT;
//初始化结点
for(int i=1;i<=n;i++){
(p+i)->weight=*(w+i-1);
(p+i)->parent=0;
(p+i)->left=0;
(p+i)->right=0;
}
//初始化哈夫曼树中除叶子结点外的结点
for(int i=n+1;i<=m;i++){
(p+i)->weight = 0;
(p+i)->parent = 0;
(p+i)->left = 0;
(p+i)->right = 0;
}
//构建
for(int i=n+1;i<=m;i++){
int s1,s2;
select(*HT,i-1,&s1,&s2);
(*HT)[s1].parent=(*HT)[s2].parent=i;
(*HT)[i].left=s1;
(*HT)[i].right=s2;
(*HT)[i].weight=(*HT)[s1].weight+(*HT)[s2].weight;
}
}
//HT为哈夫曼树,HC为存储结点哈夫曼编码的二维动态数组,n为结点的个数
void HuffmanCoding(HuffmanTree HT, HuffmanCode *HC,int n){
*HC = (HuffmanCode) malloc((n+1) * sizeof(char *));
int m=2*n-1;
int p=m;
int cdlen=0;
char *cd = (char *)malloc(n*sizeof(char));
//将各个结点的权重用于记录访问结点的次数,首先初始化为0
for (int i=1; i<=m; i++) {
HT[i].weight=0;
}
//一开始 p 初始化为 m,也就是从树根开始。一直到p为0
while (p) {
//如果当前结点一次没有访问,进入这个if语句
if (HT[p].weight==0) {
HT[p].weight=1;//重置访问次数为1
//如果有左孩子,则访问左孩子,并且存储走过的标记为0
if (HT[p].left!=0) {
p=HT[p].left;
cd[cdlen++]='0';
}
//当前结点没有左孩子,也没有右孩子,说明为叶子结点,直接记录哈夫曼编码
else if(HT[p].right==0){
(*HC)[p]=(char*)malloc((cdlen+1)*sizeof(char));
cd[cdlen]='\0';
strcpy((*HC)[p], cd);
}
}
//如果weight为1,说明访问过一次,即是从其左孩子返回的
else if(HT[p].weight==1){
HT[p].weight=2;//设置访问次数为2
//如果有右孩子,遍历右孩子,记录标记值 1
if (HT[p].right!=0) {
p=HT[p].right;
cd[cdlen++]='1';
}
}
//如果访问次数为 2,说明左右孩子都遍历完了,返回父结点
else{
HT[p].weight=0;
p=HT[p].parent;
--cdlen;
}
}
}
//打印哈夫曼编码的函数
void PrintHuffmanCode(HuffmanCode htable,int *w,int n)
{
printf("Huffman code : \n");
for(int i = 1; i <= n; i++)
printf("%d code = %s\n",w[i-1], htable[i]);
}
void main(){
int w[5] = {2, 8, 7, 6, 5};
int n = 5;
HuffmanTree htree;
HuffmanCode htable;
createHuffmanTree(&htree, w, n);
HuffmanCoding(htree, &htable, n);
PrintHuffmanCode(htable,w, n);
}
回朔法
定义:解决问题时,每进行一步,都是抱着试试看的态度,如果发现当前选择并不是最好的,或者这么走下去肯定达不到目标,立刻做回退操作重新选择。这种走不通就回退再走的方法就是回溯法。
回溯法与递归是有区别的:回溯法在列举过程如果发现当前情况根本不可能存在,就停止后续的所有工作,返回上一步进行新的尝试。递归是从问题的结果出发,不断地调用自己的思想就是递归,回溯和递归唯一的联系就是,回溯法可以用递归思想实现
使用回溯法解决问题的过程,实际上是建立一棵“状态树”的过程,回溯法的求解过程实质上是先序遍历“状态树”的过程。在某些情况下,回溯法解决问题的过程中创建的状态树并不都是满二叉树,因为在试探的过程中,有时会发现此种情况下,再往下进行没有意义,所以会放弃这条死路,回溯到上一步
B树及其基本操作(B-树就是B树)
B即Balanced,平衡的意思。B树是一种树状数据结构,概括来说是一个节点可以拥有多于2个子节点的二叉查找树
带叶子结点(F)的
不带叶子结点的
一棵m阶的B树,特性如下:
- 若根结点不是叶子结点,则至少有两棵子树,最多有m棵子树。
- 除根结点外的所有非叶子结点至少有⌈m/2⌉棵子树,最多有m棵子树。
- 所有的叶结点都出现在同一层次上,并且不带信息(可视为失败结点)。
- 所有非叶结点的结构如下:a)、Pi-1所指子树中所有结点的关键字均小于Ki b)、Pi所指子树中所有结点的关键字均大于Ki(n为关键字个数)
B树的查找
B树的查找算法如下:
①、在B树中找结点(磁盘上进行),当查找到叶结点时,查找失败。
②、在结点内的多关键字有序表中查找关键字(内存中进行):
a)、先在有序表中进行查找,若找到则查找成功。
b)、否则,根据找到的指针信息到所指的子树,执行①。
对于含有n个关键字的B树的查找,磁盘I/O次数也就是树的高度h(不包含叶结点)满足h <= log⌈m/2⌉((n+1)/2)+1
B树的插入
B树的插入过程如下:
1)、查找:利用B树的查找算法,找出插入该关键字的最底层中某个非叶结点。
2)、插入:当插入后的结点关键字个数小于m,则可以直接插入;如果等于m,则必须对结点进行分裂。
一棵3阶B树的分裂过程及方法如图2-1所示:
若此时导致其父结点的关键字个数也超过了上限,则继续进行这种分裂操作。若最终使得根结点分裂,B树的高度增1。
B树的删除
为使删除后的结点中的关键字个数 >= ⌈m/2⌉-1,将涉及结点的“合并”问题。
(1)当被删除的关键字k在非叶结点中时:
①、如果小于(大于)k的子树中关键字个数 > ⌈m/2⌉-1,则找出k的前驱(后继)值k’,并且用k’来取代k,再递归地按此方法删除k’。
②、如果前后两个子树中关键字个数均为⌈m/2⌉-1,则将两个子结点合并,直接删除k。如图3-1所示为某4阶B树的一部分。
(2)当被删除的关键字k在叶结点中时:
①、若该结点的关键字个数 > ⌈m/2⌉-1,则直接删除该关键字。
②、若该结点的关键字个数 = ⌈m/2⌉-1,且与此结点相邻的左(右)兄弟结点的关键字个数 >= ⌈m/2⌉,需要调整该结点、左(右)兄弟结点以及其双亲结点(父子换位法),以达到新的平衡,如图3-2所示。
若该结点关键字个数 = ⌈m/2⌉-1,且与该结点相邻的左(右)兄弟结点的关键字个数 = ⌈m/2⌉-1,则将该关键字删除后与左(右)兄弟结点及双亲结点中的关键字进行合并,如下图所示。
在合并的过程中,若此时导致其父结点的关键字个数也不满足B树的定义,则继续进行这种合并操作。
若最终使得根结点被合并,B树高度减1。
B+树的基本概念
B+树是B-树的变体,B+树把数据都存储在叶结点,而内部结点只存关键字和孩子指针
- 每个分支结点最多有m棵子树。
- 非叶根结点至少有两棵子树,其他每个分支结点至少有⌈m/2⌉棵子树。
- n棵子树结点含有n个关键字
- 所有叶结点包含全部关键字及指针,而且叶结点中将关键字按大小顺序排列,并且相邻叶结点按大小顺序相互链接起来。
- 所有分支结点中仅包含它的各个子结点中关键字的最大值及指向其子结点的指针。
B+树的插入操作
(1)若为空树,创建一个叶子结点,记录插入,这个叶子结点也是根结点,插入操作结束
(2)针对叶子类型结点:根据关键字找到叶子结点,然后插入记录,插入后若当前结点关键字个数<=m-1,则插入结束。否则,将这个叶子结点分裂成为左右两个叶子结点,左叶子结点包括前m/2个关键字,右叶子结点包括剩下的,将第m/2+1个记录上移到父结点(必须索引结点)。进位到父结点的关键字左孩子指针指向左结点,右孩子结点指针指向右结点,本身就指向父结点。
(3)针对索引类型结点:若当前结点的关键字个数<=m-1,则插入结束。否则,将这个索引类型结点分裂成两个索引结点,左索引结点关键字包含前(m-1)/2个,右索引结点包含m-((m-1)/2)个,将第m/2个关键字上移到父结点中,类同第2步。
B+树的删除操作
如果叶子结点中没有要删除的关键字,则删除失败,否则执行下面步骤
(1)删除叶子结点中对应的关键字。删除后若结点关键字个数>=ceil(m/2)-1,删除操作结束,否则执行下一步
(2)若兄弟结点的关键字个数>ceil(m/2)-1,向兄弟结点借一个关键字,同时用借的关键字替换(当前结点与父结点相同的关键字),删除结束,否则执行下一步。
(3)当前结点与兄弟结点合并成新的叶子结点,并删除父结点中的关键字(孩子指针变成一个指针,正好指向新的叶子结点),当前结点指向父结点(索引结点)。执行下一步。
(4)若索引结点的关键字个数>=ceil(m/2)-1,则删除操作结束,否则执行下一步。
(5)若兄弟结点关键字个数>ceil(m/2)-1,父结点关键字下移,兄弟结点关键字上移,删除结束,否则执行下一步。
(6)当前结点和兄弟结点及其父结点关键字下移合并成新的结点,将新结点指向父结点,重复第4步。
B树与B+树的区别
- 在B树中,具有n个关键字的结点含有(n+1)棵子树;而在B+树中,具有n个关键字的结点只含有n棵子树,即每个关键字对应一棵子树。
- 在B树中,根结点的关键字个数n的范围是1 <= n <= m-1,非根结点的范围是⌈m/2⌉-1<=n <= m-1;而在B+树中,根结点的关键字个数n的范围是1 <= n <= m,非根结点的范围是⌈m/2⌉<=n <= m。
- 在B+树中,所有非叶结点仅起到索引作用,即结点中的每个索引项只含有对应子树的最大关键字和指向该子树的指针,不含有该关键字对应记录的存储地址。
- 在B树中,叶结点包含的关键字和其他结点包含的关键字是不重复的;而在B+树中,叶结点包含了全部关键字,即其他非叶结点中的关键字包含在叶结点中。
5.3:森林
定义:由多个互不相交的树组成的集合称为森林
操作:森林转二叉树、二叉树转森林、树和森林的遍历(先序遍历、后序遍历)
6.图
数据之间的关系有 3 种,分别是 "一对一"、"一对多" 和 "多对多",前两种关系的数据可分别用线性表和树结构存储,"多对多"逻辑关系数据的结构用图结构存储
定义:由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),G表示一个图,V是图G中顶点的集合,E是图G中边的集合,带权的图称为网,入度/出度/度(有向图的弧 e = 所有顶点入度的和 = 所有顶点出度的和)、邻接点、
分类:有向图、无向图
存储结构:邻接矩阵(数组)表示法、邻接表(链式)表示法
操作:遍历(深度优先遍历、广度优先遍历)、构建最小生成树(普里姆算法(将顶点归并)、克鲁斯卡尔算法(将边归并))、拓扑排序、关键路径、最短路径
有向图、入度、出度、度
用<A,D>表示有向图中A到D带方向的线
无向图
用(A,D)表示无向图中连接A与D之间的线
路径和回路
无论是无向图还是有向图,从一个顶点到另一顶点途径的所有顶点组成的序列(包含这两个顶点),称为一条路径。如果路径中第一个顶点和最后一个顶点相同,则此路径称为"回路"(或"环")。若路径中各顶点都不重复,此路径又被称为"简单路径"。若回路中的顶点互不重复,此回路被称为"简单回路"(或简单环)。如{V1,V3,V4,V1},这是一个回路(环),而且还是一个简单回路(简单环)。
权和网
在某些实际场景中,图的每条边(或弧)会赋予一个实数来表示一定的含义,这种与边(或弧)相匹配的实数被称为"权",而带权的图通常称为网
图的存储
根据不同的特征,图又可分为完全图,连通图、稀疏图和稠密图
完全图
图中各个顶点都与除自身外的其他顶点有关系,若是具有 n 个顶点的完全图,图中边的数量为 n(n-1)/2;而对于具有 n 个顶点的有向完全图,图中弧的数量为 n(n-1)。
稀疏图和稠密图
这两种图是相对存在的,即如果图中具有很少的边(或弧),此图就称为"稀疏图";反之,则称此图为"稠密图"。稀疏和稠密的判断条件是:e<nlogn,其中 e 表示图中边(或弧)的数量,n 表示图中顶点的数量。如果式子成立,则为稀疏图;反之为稠密图。
连通图
图中任意两个顶点都是连通的,则称G是连通图
连通分量:若无向图不是连通图,但图中存储某个子图符合连通图的性质,则称该子图(这里的子图指的是图中"最大"的连通子图)为连通分量,如图1中的图2和图3。如果无向图是连通图,则其无法分解出多个最大连通子图。
强连通图:有向图中,若任意两个顶点 Vi 和 Vj,满足从 Vi 到 Vj 以及从 Vj 到 Vi 都连通 ,如图V1到V4连通(V1->V3->V4),V4到V1连通(V4->V1)。
强联通分量:若有向图本身不是强连通图,但其包含的最大连通子图具有强连通图的性质,则称该子图为强连通分量。如图 ,整个有向图虽不是强连通图,但其含有两个强连通分量
生成树:对连通图进行遍历,过程中所经过的边和顶点的组合可看做是一棵普通树,通常称为生成树。如图 a) 是一张连通图,图 b) 是其对应的 2 种生成树。遍历连通图的方式有多种,往往一张连通图可能有多种不同的生成树与之对应。连通图的生成树具有这样的特征,即生成树中边的数量 = 顶点数 - 1
。
连通图中的生成树必须满足以下 2 个条件:
- 包含连通图中所有的顶点;
- 任意两顶点之间有且仅有一条通路;
生成森林:生成森林是对应非连通图来说的,非连通图可分解为多个连通分量,而每个连通分量又各自对应多个生成树,因此是由多棵生成树组成的生成森林。
图 3 中列出的仅是各个连通分量的其中一种生成树。
图的存储
图的邻接矩阵存储(引用:https://blog.csdn.net/weixin_39791665/article/details/105137786)
/*
顶点数据类型为char,边上权值类型为int
*/
#include <stdio.h>
#include <stdlib.h>
#define MAXVEX 100 //最大顶点数
#define INFINITY 99999 //用99999表示无限大
//********************邻接矩阵存储结构代码********************
typedef struct
{
char verx[MAXVEX]; //顶点表
int arc[MAXVEX][MAXVEX]; //边表
int numVertexes, numEdges; //图中当前顶点数和边数
}MGraph;
//********************建立无向图的邻接矩阵表示********************
void CreateMGraph(MGraph* G)
{
int i, j, k, w;
printf("输入顶点数和边数\n");
scanf("%d%d", &G->numVertexes, &G->numEdges);
getchar(); //获取缓冲区的回车符
for (i = 0; i < G->numVertexes; i++) //读入顶点信息
{
printf("输入第%d个顶点信息\n",i+1);
scanf("%c",&G->verx[i]);
getchar(); //获取缓冲区的回车符
}
for (i = 0; i < G->numVertexes; i++) //矩阵初始化
for (j = 0; j < G->numVertexes; j++)
if (i == j) //如果i==j;矩阵的主对角线都为0
G->arc[i][j] = 0;
else G->arc[i][j] = INFINITY; //否则,矩阵初始化,都是无穷大
for (k = 0; k < G->numEdges; k++) //建立邻接矩阵
{
printf("输入边(vi,vj)上的下标i,下标j和权w:\n");
scanf("%d%d%d", &i, &j, &w); //输入边(vi,vj)上的权值w
G->arc[i][j] = w;
G->arc[j][i] = G->arc[i][j]; //因为无向图矩阵是对称的
}
}
//********************邻接矩阵的输出********************
void prt(MGraph G)
{
int i,j;
for (i = 0; i < G.numVertexes; i++)
{
for (j = 0; j < G.numVertexes; j++)
printf("%-10d", G.arc[i][j]);
printf("\n");
}
}
int main()
{
MGraph G;
CreateMGraph(&G);
prt(G);
}
图的邻接表存储
/*
顶点数据类型为char,边上权值类型为int
*/
#include <stdio.h>
#include <stdlib.h>
#define MAXVEX 100 //最大顶点数
#define INFINITY 99999 //用99999表示无限大
//****************************边表结点****************************
typedef struct EdgeNode
{
int adjvex; //邻接点域,储存该顶点对应的下标
//int weight; //用于储存权值,非网图不需要
struct EdgeNode* next; //链域,指向下一个邻接结点
}EdgeNode;
//****************************顶点表结点****************************
typedef struct VertexNode
{
char data; //顶点域存放顶点信息
EdgeNode* firstedge; //边表头指针
}VertexNode, AdjList[MAXVEX];
//****************************邻接表结构****************************
typedef struct GraphAdjList
{
AdjList adjlist; //顶点表结点数组
int numVertexes, numEdges; //图中当前顶点数和边数
}GraphAdjList;
//**************************无向图邻接表的创建*************************
void CreateALGraph(GraphAdjList* G)
{
int i, j, k;
EdgeNode* e;
printf("输入顶点数和边数\n");
scanf("%d%d", &G->numVertexes, &G->numEdges);
getchar(); //获取缓冲区的 回车符
for (i = 0; i < G->numVertexes; i++) //读入顶点信息,建立顶点表
{
printf("输入第%d个顶点值\n", i + 1);
scanf("%c", &G->adjlist[i].data); //输入顶点信息
getchar(); //获取缓冲区的 回车符
G->adjlist[i].firstedge = NULL; //将边表置为空表
}
for (k = 0; k < G->numEdges; k++) //建立边表
{
printf("输入第%d条边(vi,vj)的下标:\n",k+1);
scanf("%d%d", &i, &j);
e = (EdgeNode*)malloc(sizeof(EdgeNode)); //向内存申请空间
if (e == NULL)
{
printf("内存申请失败\n");
exit(0);
}
e->adjvex = j; //这三步,类似于单链表的头插法
e->next = G->adjlist[i].firstedge;
G->adjlist[i].firstedge = e;
e = (EdgeNode*)malloc(sizeof(EdgeNode));
if (e == NULL)
{
printf("内存申请失败\n");
exit(0);
}
e->adjvex = i;
e->next = G->adjlist[j].firstedge;
G->adjlist[j].firstedge = e;
}
}
//********************邻接矩阵的输出********************
void prt(GraphAdjList G)
{
EdgeNode* p;
int i;
for (i = 0; i < G.numVertexes; i++) //i小于当前顶点数
{
p = G.adjlist[i].firstedge; //p指向第一个边表结点
while (p != NULL)
{
printf("边%c-%c\t", G.adjlist[i].data, G.adjlist[p->adjvex].data);
p = p->next; //p指向下一个边表结点
}
printf("\n");
}
}
int main()
{
GraphAdjList G;
CreateALGraph(&G);
prt(G);
return 0;
}
图的十字链表存储(略)
图的邻接多重表存储(略)
图的遍历
深度优先搜索
V1 -> V2 -> V4 -> V8 -> V5 -> V3 -> V6 -> V7 //深度优先搜索
深度优先生成树
对无向图进行遍历的时候,遍历过程中所经历过的图中的顶点和边的组合,就是图的生成树或者生成森林
非连通图在进行遍历时,实则是对非连通图中每个连通分量分别进行遍历,在遍历过程经过的每个顶点和边,就构成了每个连通分量的生成树。非连通图中,多个连通分量构成的多个生成树为非连通图的生成森林
广度优先搜索
V1 -> V2 -> v3 -> V4 -> V5 -> V6 -> V7 -> V8
广度优先生成树
求最小生成树
Prim算法
算法设计:设最小生成树的顶点集合为U1,最小生成树边的集合为T1,原图顶点集合为U2,原图边的集合为T2
- 任选图中一个顶点加入U1
- 选取U1中点与U2中点连接成最小的邻边,将该边在U2中的顶点移动到U1,将此边加入T1中
- 重复步骤2直到边数=顶点数-1
typedef struct//邻接矩阵结构体
{
VertexType Vertex[VertexMax];//存放顶点元素的一维数组
int AdjMatrix[VertexMax][VertexMax];//邻接矩阵二维数组
int vexnum,arcnum;//图的顶点数和边数
}MGraph;
typedef struct//最短路径数组结构体(候选最短边)
{
VertexType adjvex;//候选最短边的邻接点
int lowcost;//候选最短边的权值
}ShortEdge;
int LocateVex(MGraph *G,VertexType v)//查找元素v在一维数组 Vertex[] 中的下标,并返回下标
{
int i;
for(i=0;i<G->vexnum;i++)
{
if(v==G->Vertex[i])
{
return i;
}
}
printf("No Such Vertex!\n");
return -1;
}
void CreateUDN(MGraph *G)//构建无向网(Undirected Network)
{
int i,j;
//1.输入顶点数和边数
printf("输入顶点个数和边数:\n");
printf("顶点数 n=");
scanf("%d",&G->vexnum);
printf("边 数 e=");
scanf("%d",&G->arcnum);
printf("\n");
printf("\n");
//2.输入顶点元素
printf("输入顶点元素(无需空格隔开):");
scanf("%s",G->Vertex);
printf("\n");
//3.矩阵初始化
for(i=0;i<G->vexnum;i++)
for(j=0;j<G->vexnum;j++)
{
G->AdjMatrix[i][j]=MaxInt;
}
//4.构建邻接矩阵
int n,m;
VertexType v1,v2;
int w;//v1->v2的权值
printf("请输入边的信息和权值(例:AB,15):\n");
for(i=0;i<G->arcnum;i++)
{
printf("输入第%d条边信息及权值:",i+1);
scanf(" %c%c,%d",&v1,&v2,&w);
n=LocateVex(G,v1); //获取v1所对应的在Vertex数组中的坐标
m=LocateVex(G,v2); //获取v2所对应的在Vertex数组中的坐标
if(n==-1||m==-1)
{
printf("NO This Vertex!\n");
return;
}
G->AdjMatrix[n][m]=w;
G->AdjMatrix[m][n]=w;//无向网仅此处不同
}
}
void print(MGraph G)
{
int i,j;
printf("\n-------------------------------");
printf("\n 邻接矩阵:\n\n");
printf("\t ");
for(i=0;i<G.vexnum;i++)
printf("\t%c",G.Vertex[i]);
printf("\n");
for(i=0;i<G.vexnum;i++)
{
printf("\t%c",G.Vertex[i]);
for(j=0;j<G.vexnum;j++)
{
if(G.AdjMatrix[i][j]==MaxInt)
printf("\t∞");
else printf("\t%d",G.AdjMatrix[i][j]);
}
printf("\n");
}
}
int minimal(MGraph *G,ShortEdge *shortedge)
{
int i,j;
int min,loc;
min=MaxInt;
for(i=1;i<G->vexnum;i++)
{
if(min>shortedge[i].lowcost&&shortedge[i].lowcost!=0)
{
min=shortedge[i].lowcost;
loc=i;
}
}
return loc;
}
void MiniSpanTree_Prim(MGraph *G,VertexType start)
{
int i,j,k;
ShortEdge shortedge[VertexMax];
//1.处理起始点start
k=LocateVex(G,start);
for(i=0;i<G->vexnum;i++)
{
shortedge[i].adjvex=start;
shortedge[i].lowcost=G->AdjMatrix[k][i];
}
shortedge[k].lowcost=0;//lowcost为0表示该顶点属于U集合
//2.处理后续结点
for(i=0;i<G->vexnum-1;i++)//对集合U,去找最短路径的顶点
{
k=minimal(G,shortedge);//找最短路径的顶点
printf("%c->%c,%d\n",shortedge[k].adjvex,G->Vertex[k],shortedge[k].lowcost);//输出找到的最短路径顶,及路径权值
shortedge[k].lowcost=0;//将找到的最短路径顶点加入集合U中
for(j=0;j<G->vexnum;j++)//U中加入了新节点,可能出现新的最短路径,故更新shortedge数组
{
if(G->AdjMatrix[k][j]<shortedge[j].lowcost)//有更短路径出现时,将其替换进shortedge数组
{
shortedge[j].lowcost=G->AdjMatrix[k][j];
shortedge[j].adjvex=G->Vertex[k];
}
}
}
}
int main()
{
VertexType start;
MGraph G;
CreateUDN(&G);
print(G);
printf("请输入起始点:");
scanf(" %c",&start);//%c前面有空格
MiniSpanTree_Prim(&G,start);
return 0;
}
Krasual算法
算法设计:设最小生成树的顶点集合为U1,最小生成树边的集合为T1,原图顶点集合为U2,原图边的集合为T2
- 将原图中的顶点加入到T1
- 任选两个顶点连线使得边的权值最小,若把该边加入到T1中不会形成回路(形成回路就不是树了)则加入,否则不加入
- 重复步骤2直到边数=顶点数-1
typedef struct
{
VertexType begin;
VertexType end;
int weight;
}Edge;//边集数组edge[]的单元
typedef struct
{
VertexType Vertex[VertexMax];//顶点数组
Edge edge[VertexMax];//边集数组
int vexnum;//顶点数
int edgenum;//边数
}EdgeGraph;
void CreateEdgeGraph(EdgeGraph *E)
{
int i;
printf("请输入顶点数和边数:\n");
printf("顶点数 n=");
scanf("%d",&E->vexnum);
printf("边 数 e=");
scanf("%d",&E->edgenum);
printf("\n");
//printf("\n");
printf("输入顶点(无需空格隔开):");
scanf("%s",E->Vertex);
printf("\n\n");
printf("输入边信息和权值(如:AB,15):\n");
for(i=0;i<E->edgenum;i++)
{
printf("请输入第%d边的信息:",i+1);
scanf(" %c%c,%d",&E->edge[i].begin,&E->edge[i].end,&E->edge[i].weight);
}
}
void print(EdgeGraph *E)
{
int i;
printf("\n-----------------------------------\n");
printf(" 顶点数组Vertex:");
for(i=0;i<E->vexnum;i++)
{
printf("%c ",E->Vertex[i]);
}
printf("\n\n");
printf(" 边集数组edge:\n\n");
printf("\t\tBegin End Weight\n");
for(i=0;i<E->edgenum;i++)
{
printf("\tedge[%d] %c %c %d\n",i,E->edge[i].begin,E->edge[i].end,E->edge[i].weight);
}
printf("\n-----------------------------------\n");
}
void sort(EdgeGraph *E)
{
int i,j;
Edge temp;
for(i=0;i<E->edgenum-1;i++)
{
for(j=i+1;j<E->edgenum;j++)
{
if(E->edge[i].weight>E->edge[j].weight)
{
temp=E->edge[i];
E->edge[i]=E->edge[j];
E->edge[j]=temp;
}
}
}
}
int LocateVex(EdgeGraph *E,VertexType v)//查找元素v在一维数组 Vertex[] 中的下标,并返回下标
{
int i;
for(i=0;i<E->vexnum;i++)
{
if(v==E->Vertex[i])
{
return i;
}
}
printf("No Such Vertex!\n");
return -1;
}
int FindRoot(int t,int parent[])//t接收到是结点在Vertex数组中的下标
{
while(parent[t]>-1)//parent=-1表示没有双亲,即没有根节点
{
t=parent[t];//逐代查找根节点
}
return t;//将找到的根节点返回,若没有根节点返回自身
}
void MiniSpanTree_Kruskal(EdgeGraph *E)
{
int i;
int num;//生成边计数器,当num=顶点数-1 就代表最小生成树生成完毕
int root1,root2;
int LocVex1,LocVex2;
int parent[VertexMax];//用于查找顶点的双亲,判断两个顶点间是否有共同的双亲,来判断生成树是否会成环
//1.按权值从小到大排序
sort(E);
print(E);
//2.初始化parent数组
for(i=0;i<E->vexnum;i++)
{
parent[i]=-1;
}
printf("\n 最小生成树(Kruskal):\n\n");
//3.
for(num=0,i=0;i<E->edgenum;i++)
{
LocVex1=LocateVex(E,E->edge[i].begin);
LocVex2=LocateVex(E,E->edge[i].end);
root1=FindRoot(LocVex1,parent);
root2=FindRoot(LocVex2,parent);
if(root1!=root2)//若不会成环,则在最小生成树中构建此边
{
printf("\t\t%c-%c w=%d\n",E->edge[i].begin,E->edge[i].end,E->edge[i].weight);//输出此边
parent[root2]=root1;//合并生成树
num++;
if(num==E->vexnum-1)//若num=顶点数-1,代表树生成完毕,提前返回
{
return;
}
}
}
}
int main()
{
EdgeGraph E;
CreateEdgeGraph(&E);
MiniSpanTree_Kruskal(&E);
return 0;
}
拓扑排序
AOV网:把顶点表示活动、边表示活动间先后关系的有向图称做顶点活动网(Activity On Vertex network),简称AOV网。
判定给定AOE网是否存在环的方法:网中的顶点都在其拓扑序列中则一定不存在环。
拓扑排序是对有向无环图的顶点的一种排序,拓扑排序的思想是:
- 从有向图中选择一个入度为0的顶点输出;
- 从有向图中删去此顶点,并删除以此顶点为尾的弧;
- 重复上述两步,直到输出全部顶点或者不存在入度为0的顶点为止(后面的情况表明图中有环)
#include "stdio.h"
#include "stdlib.h"
#include "io.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXEDGE 20
#define MAXVEX 14
#define INFINITY 65535
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
/* 邻接矩阵结构 */
typedef struct
{
int vexs[MAXVEX];
int arc[MAXVEX][MAXVEX];
int numVertexes, numEdges;
}MGraph;
/* 邻接表结构****************** */
typedef struct EdgeNode /* 边表结点 */
{
int adjvex; /* 邻接点域,存储该顶点对应的下标 */
int weight; /* 用于存储权值,对于非网图可以不需要 */
struct EdgeNode *next; /* 链域,指向下一个邻接点 */
}EdgeNode;
typedef struct VertexNode /* 顶点表结点 */
{
int in; /* 顶点入度 */
int data; /* 顶点域,存储顶点信息 */
EdgeNode *firstedge;/* 边表头指针 */
}VertexNode, AdjList[MAXVEX];
typedef struct
{
AdjList adjList;
int numVertexes,numEdges; /* 图中当前顶点数和边数 */
}graphAdjList,*GraphAdjList;
/* **************************** */
void CreateMGraph(MGraph *G)/* 构件图 */
{
int i, j;
/* printf("请输入边数和顶点数:"); */
G->numEdges=MAXEDGE;
G->numVertexes=MAXVEX;
for (i = 0; i < G->numVertexes; i++)/* 初始化图 */
{
G->vexs[i]=i;
}
for (i = 0; i < G->numVertexes; i++)/* 初始化图 */
{
for ( j = 0; j < G->numVertexes; j++)
{
G->arc[i][j]=0;
}
}
G->arc[0][4]=1;
G->arc[0][5]=1;
G->arc[0][11]=1;
G->arc[1][2]=1;
G->arc[1][4]=1;
G->arc[1][8]=1;
G->arc[2][5]=1;
G->arc[2][6]=1;
G->arc[2][9]=1;
G->arc[3][2]=1;
G->arc[3][13]=1;
G->arc[4][7]=1;
G->arc[5][8]=1;
G->arc[5][12]=1;
G->arc[6][5]=1;
G->arc[8][7]=1;
G->arc[9][10]=1;
G->arc[9][11]=1;
G->arc[10][13]=1;
G->arc[12][9]=1;
}
/* 利用邻接矩阵构建邻接表 */
void CreateALGraph(MGraph G,GraphAdjList *GL)
{
int i,j;
EdgeNode *e;
*GL = (GraphAdjList)malloc(sizeof(graphAdjList));
(*GL)->numVertexes=G.numVertexes;
(*GL)->numEdges=G.numEdges;
for(i= 0;i <G.numVertexes;i++) /* 读入顶点信息,建立顶点表 */
{
(*GL)->adjList[i].in=0;
(*GL)->adjList[i].data=G.vexs[i];
(*GL)->adjList[i].firstedge=NULL; /* 将边表置为空表 */
}
for(i=0;i<G.numVertexes;i++) /* 建立边表 */
{
for(j=0;j<G.numVertexes;j++)
{
if (G.arc[i][j]==1)
{
e=(EdgeNode *)malloc(sizeof(EdgeNode));
e->adjvex=j; /* 邻接序号为j */
e->next=(*GL)->adjList[i].firstedge; /* 将当前顶点上的指向的结点指针赋值给e */
(*GL)->adjList[i].firstedge=e; /* 将当前顶点的指针指向e */
(*GL)->adjList[j].in++;
}
}
}
}
/* 拓扑排序,若GL无回路,则输出拓扑排序序列并返回1,若有回路返回0。 */
Status TopologicalSort(GraphAdjList GL)
{
EdgeNode *e;
int i,k,gettop;
int top=0; /* 用于栈指针下标 */
int count=0;/* 用于统计输出顶点的个数 */
int *stack; /* 建栈将入度为0的顶点入栈 */
stack=(int *)malloc(GL->numVertexes * sizeof(int) );
for(i = 0; i<GL->numVertexes; i++)
if(0 == GL->adjList[i].in) /* 将入度为0的顶点入栈 */
stack[++top]=i;
while(top!=0)
{
gettop=stack[top--];
printf("%d -> ",GL->adjList[gettop].data);
count++; /* 输出i号顶点,并计数 */
for(e = GL->adjList[gettop].firstedge; e; e = e->next)
{
k=e->adjvex;
if( !(--GL->adjList[k].in) ) /* 将i号顶点的邻接点的入度减1,如果减1后为0,则入栈 */
stack[++top]=k;
}
}
printf("\n");
if(count < GL->numVertexes)
return ERROR;
else
return OK;
}
int main(void)
{
MGraph G;
GraphAdjList GL;
int result;
CreateMGraph(&G);
CreateALGraph(G,&GL);
result=TopologicalSort(GL);
printf("result:%d",result);
return 0;
}
关键路径
AOE网:把边表示活动,顶点表示事件,权表示活动持续时间的带权有向无环图称做边活动网(Activity on Edge),简称AOE网。
关键路径:在AOE网中,从开始点到完成点的最长路径的长度(该路径上的各个活动所持续的时间之和)这个最长路径称为关键路径。关键路径长度是整个工程所需的最短工期
关键活动:关键路径上的活动称为关键活动。
V1事件:表示整个工程开始;V9事件:表示整个工程结束;V5事件:表示a4,a5已经完成,a7,a8可以开始
为了求出一个给定 AOE 网的关键路径,需要知道以下 4 个统计数据:
- 对于 AOE 网中的顶点(事件)有两个时间:最早发生时间(用 Ve(j) 表示)和最晚发生时间(用 Vl(j) 表示);
- 对于边(活动)来说,也有两个时间:最早开始时间(用 e(i) 表示)和最晚开始时间( l(i) 表示)。
首先完成 Ve(j)、Vl(j)、e(i)、l(i) 4 种统计信息的准备工作。
求事件的最晚开始时间是从前往后算的 ;求事件的最晚开始时间是从后往前算的
然后根据事件的俩个时间来算活动的时间 ,其中设活动 ai 用弧 < j, k > 表示,其持续时间记为:wj,k
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTEX_NUM 20//最大顶点个数
#define VertexType int//顶点数据的类型
typedef enum{false,true} bool;
//建立全局变量,保存边的最早开始时间
VertexType ve[MAX_VERTEX_NUM];
//建立全局变量,保存边的最晚开始时间
VertexType vl[MAX_VERTEX_NUM];
typedef struct ArcNode{
int adjvex;//邻接点在数组中的位置下标
struct ArcNode * nextarc;//指向下一个邻接点的指针
VertexType dut;
}ArcNode;
typedef struct VNode{
VertexType data;//顶点的数据域
ArcNode * firstarc;//指向邻接点的指针
}VNode,AdjList[MAX_VERTEX_NUM];//存储各链表头结点的数组
typedef struct {
AdjList vertices;//图中顶点及各邻接点数组
int vexnum,arcnum;//记录图中顶点数和边或弧数
}ALGraph;
//找到顶点对应在邻接表数组中的位置下标
int LocateVex(ALGraph G,VertexType u){
for (int i=0; i<G.vexnum; i++) {
if (G.vertices[i].data==u) {
return i;
}
}
return -1;
}
//创建AOE网,构建邻接表
void CreateAOE(ALGraph **G){
*G=(ALGraph*)malloc(sizeof(ALGraph));
scanf("%d,%d",&((*G)->vexnum),&((*G)->arcnum));
for (int i=0; i<(*G)->vexnum; i++) {
scanf("%d",&((*G)->vertices[i].data));
(*G)->vertices[i].firstarc=NULL;
}
VertexType initial,end,dut;
for (int i=0; i<(*G)->arcnum; i++) {
scanf("%d,%d,%d",&initial,&end,&dut);
ArcNode *p=(ArcNode*)malloc(sizeof(ArcNode));
p->adjvex=LocateVex(*(*G), end);
p->nextarc=NULL;
p->dut=dut;
int locate=LocateVex(*(*G), initial);
p->nextarc=(*G)->vertices[locate].firstarc;
(*G)->vertices[locate].firstarc=p;
}
}
//结构体定义栈结构
typedef struct stack{
VertexType data;
struct stack * next;
}stack;
stack *T;
//初始化栈结构
void initStack(stack* *S){
(*S)=(stack*)malloc(sizeof(stack));
(*S)->next=NULL;
}
//判断栈是否为空
bool StackEmpty(stack S){
if (S.next==NULL) {
return true;
}
return false;
}
//进栈,以头插法将新结点插入到链表中
void push(stack *S,VertexType u){
stack *p=(stack*)malloc(sizeof(stack));
p->data=u;
p->next=NULL;
p->next=S->next;
S->next=p;
}
//弹栈函数,删除链表首元结点的同时,释放该空间,并将该结点中的数据域通过地址传值给变量i;
void pop(stack *S,VertexType *i){
stack *p=S->next;
*i=p->data;
S->next=S->next->next;
free(p);
}
//统计各顶点的入度
void FindInDegree(ALGraph G,int indegree[]){
//初始化数组,默认初始值全部为0
for (int i=0; i<G.vexnum; i++) {
indegree[i]=0;
}
//遍历邻接表,根据各链表中结点的数据域存储的各顶点位置下标,在indegree数组相应位置+1
for (int i=0; i<G.vexnum; i++) {
ArcNode *p=G.vertices[i].firstarc;
while (p) {
indegree[p->adjvex]++;
p=p->nextarc;
}
}
}
bool TopologicalOrder(ALGraph G){
int indegree[G.vexnum];//创建记录各顶点入度的数组
FindInDegree(G,indegree);//统计各顶点的入度
//建立栈结构,程序中使用的是链表
stack *S;
//初始化栈
initStack(&S);
for (int i=0; i<G.vexnum; i++) {
ve[i]=0;
}
//查找度为0的顶点,作为起始点
for (int i=0; i<G.vexnum; i++) {
if (!indegree[i]) {
push(S, i);
}
}
int count=0;
//栈为空为结束标志
while (!StackEmpty(*S)) {
int index;
//弹栈,并记录栈中保存的顶点所在邻接表数组中的位置
pop(S,&index);
//压栈,为求各边的最晚开始时间做准备
push(T, index);
++count;
//依次查找跟该顶点相链接的顶点,如果初始入度为1,当删除前一个顶点后,该顶点入度为0
for (ArcNode *p=G.vertices[index].firstarc; p ; p=p->nextarc) {
VertexType k=p->adjvex;
if (!(--indegree[k])) {
//顶点入度为0,入栈
push(S, k);
}
//如果边的源点的最长路径长度加上边的权值比汇点的最长路径长度还长,就覆盖ve数组中对应位置的值,最终结束时,ve数组中存储的就是各顶点的最长路径长度。
if (ve[index]+p->dut>ve[k]) {
ve[k]=ve[index]+p->dut;
}
}
}
//如果count值小于顶点数量,表明有向图有环
if (count<G.vexnum) {
printf("该图有回路");
return false;
}
return true;
}
//求各顶点的最晚发生时间并计算出各边的最早和最晚开始时间
void CriticalPath(ALGraph G){
if (!TopologicalOrder(G)) {
return ;
}
for (int i=0 ; i<G.vexnum ; i++) {
vl[i]=ve[G.vexnum-1];
}
int j,k;
while (!StackEmpty(*T)) {
pop(T, &j);
for (ArcNode* p=G.vertices[j].firstarc ; p ; p=p->nextarc) {
k=p->adjvex;
//构建Vl数组,在初始化时,Vl数组中每个单元都是18,如果每个边的汇点-边的权值比源点值小,就保存更小的。
if (vl[k]-p->dut<vl[j]) {
vl[j] = vl[k]-p->dut;
}
}
}
for (j = 0; j < G.vexnum; j++) {
for (ArcNode*p = G.vertices[j].firstarc; p ;p = p->nextarc) {
k = p->adjvex;
//求各边的最早开始时间e[i],等于ve数组中相应源点存储的值
int ee = ve[j];
//求各边的最晚开始时间l[i],等于汇点在vl数组中存储的值减改边的权值
int el = vl[k]-p->dut;
//判断e[i]和l[i]是否相等,如果相等,该边就是关键活动,相应的用*标记;反之,边后边没标记
char tag = (ee==el)?'*':' ';
printf("%3d%3d%3d%3d%3d%2c\n",j,k,p->dut,ee,el,tag);
}
}
}
int main(){
ALGraph *G;
CreateAOE(&G);//创建AOE网
initStack(&T);
TopologicalOrder(*G);
CriticalPath(*G);
return 0;
}
最短路径
从某顶点出发,沿图的边到达另一顶点所经过的路径中,各边上权值之和最小的一条路径叫做最短路径
迪杰斯塔拉(Dijkstra)算法——单源最短路径
声明一个数组dis来保存源点到各个顶点的最短距离和一个保存已经找到了最短路径的顶点的集合:T,初始时,集合T只有源点
T={v1}
T={v1,v3}
T={v1,v3,v5}
T={v1,v3,v5,v4}
T={v1,v3,v5,v4,v6}
#include<stdio.h>
#include<stdlib.h>
#define MaxVexNum 50
#define MaxInt 32767
#define MaxEdgeNum 50
//邻接矩阵
typedef int VertexType;
typedef int EdgeType;
typedef struct AMGraph{
VertexType vexs[MaxVexNum];//顶点表
EdgeType arcs[MaxVexNum][MaxVexNum];//邻接矩阵表
int vexnum,edgenum;//顶点数,边数
}AMGraph;
void createGraph(AMGraph &g){//创建无向图
printf("请输入顶点数:");
scanf("%d",&g.vexnum);
printf("\n请输入边数:");
scanf("%d",&g.edgenum);
//初始化顶点表
for(int i=0;i<g.vexnum;i++){
g.vexs[i]=i;
}
for(int i=0;i<g.vexnum;i++){
for(int j=0;j<g.vexnum;j++){
g.arcs[i][j]=MaxInt;
if(i==j) g.arcs[i][j]=0;
}
}
printf("请输入边的信息以及边的权值(顶点是0~n-1)\n");
for(int i=0;i<g.edgenum;i++){
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
g.arcs[x][y]=w;
//g.arcs[y][x]=w;
}
}
void PrintGraph(AMGraph g){
printf("邻接矩阵为:\n");
for(int i=0;i<g.vexnum;i++) {
printf(" %d",g.vexs[i]);
}
printf("\n");
for(int i=0;i<g.vexnum;i++){
printf("%d ",g.vexs[i]);
for(int j=0;j<g.vexnum;j++){
if(g.arcs[i][j]==32767){
printf("∞ ");
}else{
printf("%d ",g.arcs[i][j]);
}
}
printf("\n");
}
}
//Dijkstra算法,求单源最短路径
void Dijkstra(AMGraph g,int dist[],int path[],int v0){
int n=g.vexnum,v;
int set[n];//set数组用于记录该顶点是否归并
//第一步:初始化
for(int i=0;i<n;i++){
set[i]=0;
dist[i]=g.arcs[v0][i];
if(dist[i]<MaxInt){//若距离小于MaxInt说明两点之间有路可通
path[i]=v0;//则更新路径i的前驱为v
}else{
path[i]=-1; //表示这两点之间没有边
}
}
set[v0]=1;//将初始顶点并入
path[v0]=-1;//初始顶点没有前驱
//第二步
for(int i=1;i<n;i++){//共n-1个顶点
int min=MaxInt;
//第二步:从i=1开始依次选一个距离顶点的最近顶点
for(int j=0;j<n;j++){
if(set[j]==0&&dist[j]<min){
v=j;
min=dist[j];
}
}
//将顶点并入
set[v]=1;
//第三步:在将新结点并入后,其初始顶点v0到各顶点的距离将会发生变化,所以需要更新dist[]数组
for(int j=0;j<n;j++){
if(set[j]==0&&dist[v]+g.arcs[v][j]<dist[j]){
dist[j]=dist[v]+g.arcs[v][j];
path[j]=v;
}
}
}
//输出
printf(" ");
for(int i=0;i<n;i++) printf("%d ",i);
printf("\ndist[]:");
for(int i=0;i<n;i++) printf("%d ",dist[i]);
printf("\npath[]:");
for(int i=0;i<n;i++) printf("%d ",path[i]);
}
int main(){
AMGraph g;
createGraph(g);
int dist[g.vexnum];
int path[g.vexnum];
Dijkstra(g,dist,path,0);
}
弗洛伊德(Floyd)算法——所有顶点间的最短路径
其中:
D(-1)[i][j]=arcs[i][j](arcs是原图的邻接矩阵)
D(0)[i][j]:代表vi到vj的中间顶点不大于0的最短路径长度
D(k)[i][j]:代表vi到vj的中间顶点不大于k的最短路径长度
D(n-1)[i][j]:代表vi到vj的最短路径长度(n为原图中顶点个数)
P(0)[i][j]:代表vi到vj的中间顶点不大于0的最短路径
P(k)[i][j]:代表vi到vj的中间顶点不大于k的最短路径
P(n-1)[i][j]:代表vi到vj的最短路径(n为原图中顶点个数)
#include<stdio.h>
#include<stdlib.h>
#define MaxVexNum 50
#define MaxInt 32767
#define MaxEdgeNum 50
//邻接矩阵
typedef int VertexType;
typedef int EdgeType;
typedef struct AMGraph{
VertexType vexs[MaxVexNum];//顶点表
EdgeType arcs[MaxVexNum][MaxVexNum];//邻接矩阵表
int vexnum,edgenum;//顶点数,边数
}AMGraph;
void createGraph(AMGraph &g){//创建无向图
printf("请输入顶点数:");
scanf("%d",&g.vexnum);
printf("\n请输入边数:");
scanf("%d",&g.edgenum);
//初始化顶点表
for(int i=0;i<g.vexnum;i++){
g.vexs[i]=i;
}
for(int i=0;i<g.vexnum;i++){
for(int j=0;j<g.vexnum;j++){
g.arcs[i][j]=MaxInt;
if(i==j) g.arcs[i][j]=0;
}
}
printf("请输入边的信息以及边的权值(顶点是0~n-1)\n");
for(int i=0;i<g.edgenum;i++){
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
g.arcs[x][y]=w;
//g.arcs[y][x]=w;
}
}
void PrintGraph(AMGraph g){
printf("邻接矩阵为:\n");
for(int i=0;i<g.vexnum;i++) {
printf(" %d",g.vexs[i]);
}
printf("\n");
for(int i=0;i<g.vexnum;i++){
printf("%d ",g.vexs[i]);
for(int j=0;j<g.vexnum;j++){
if(g.arcs[i][j]==32767){
printf("∞ ");
}else{
printf("%d ",g.arcs[i][j]);
}
}
printf("\n");
}
}
//Floyd算法
//递归输出两个顶点直接最短路径
void printPath(int u,int v,int path[][MaxVexNum]){
if(path[u][v]==-1){
printf("[%d %d] ",u,v);
}else{
int mid=path[u][v];
printPath(u,mid,path);
printPath(mid,v,path);
}
}
void Floyd(AMGraph g,int path[][MaxVexNum]){
int n=g.vexnum;
int A[n][n];
//第一步:初始化path[][]和A[][]数组
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
A[i][j]=g.arcs[i][j];
path[i][j]=-1;
}
}
//第二步:三重循环,寻找最短路径
for(int v=0;v<n;v++){//第一层是代表中间结点
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(A[i][j]>A[i][v]+A[v][j]){
A[i][j]=A[i][v]+A[v][j];
path[i][j]=v;
}
}
}
}
}
int main(){
AMGraph g;
createGraph(g);
PrintGraph(g);
int path[MaxVexNum][MaxVexNum];
Floyd(g,path);
printPath(1,0,path);
}
7.查找
在查找表查找某个特定元素时,前提是需要知道这个元素的一些属性,这些属性都可以称为关键字
关键字又细分为主关键字和次关键字。若某个关键字可以唯一地识别一个数据元素时,称这个关键字为主关键字,例如学生的学号就具有唯一性;反之,像学生姓名、年龄这类的关键字,由于不具有唯一性,称为次关键字。
7.1:静态查找(在查找表中只做查找操作,而不改动表中数据元素,称此类查找表为静态查找表)
顺序查找:从表中的最后一个数据元素开始,逐个同记录的关键字做比较,如果匹配成功,则查找成功;反之,如果直到表中第一个关键字查找完也没有成功匹配,则查找失败。
在程序中初始化创建查找表时,由于是顺序存储,所以将所有的数据元素存储在数组中,但是把第一个位置留给了用户用于查找的关键字。顺序表的一端添加用户用于搜索的关键字,称作“监视哨”。放置好监视哨之后,顺序表遍历从没有监视哨的一端依次进行,如果查找表中有用户需要的数据,则程序输出该位置;反之,程序会运行至监视哨,此时匹配成功,程序停止运行,但是结果是查找失败
#include <stdio.h>
#include <stdlib.h>
#define keyType int
typedef struct {
keyType key;//查找表中每个数据元素的值
//如果需要,还可以添加其他属性
}ElemType;
typedef struct{
ElemType *elem;//存放查找表中数据元素的数组
int length;//记录查找表中数据的总数量
}SSTable;
//创建查找表
void Create(SSTable **st,int length){
(*st)=(SSTable*)malloc(sizeof(SSTable));
(*st)->length=length;
(*st)->elem =(ElemType*)malloc((length+1)*sizeof(ElemType));
printf("输入表中的数据元素:\n");
//根据查找表中数据元素的总长度,在存储时,从数组下标为 1 的空间开始存储数据
for (int i=1; i<=length; i++) {
scanf("%d",&((*st)->elem[i].key));
}
}
//查找表查找的功能函数,其中key为关键字
int Search_seq(SSTable *st,keyType key){
st->elem[0].key=key;//将关键字作为一个数据元素存放到查找表的第一个位置,起监视哨的作用
int i=st->length;
//从查找表的最后一个数据元素依次遍历,一直遍历到数组下标为0
while (st->elem[i].key!=key) {
i--;
}
//如果 i=0,说明查找失败;反之,返回的是含有关键字key的数据元素在查找表中的位置
return i;
}
int main(int argc, const char * argv[]) {
SSTable *st;
Create(&st, 6);
getchar();
printf("请输入查找数据的关键字:\n");
int key;
scanf("%d",&key);
int location=Search_seq(st, key);
if (location==0) {
printf("查找失败");
}else{
printf("数据在查找表中的位置为:%d",location);
}
return 0;
}
平均查找长度(Average Search Length,用 ASL 表示):
Pi 为第 i 个数据元素被查找的概率,Ci 表示在查找到第 i 个数据元素之前已进行过比较的次数
折半查找:只适用于有序表,且限于顺序存储结构,该算法的使用的前提是静态查找表中的数据必须是有序的。所以使用折半查找前需要对数据进行排序
对静态查找表{5,13,19,21,37,56,64,75,80,88,92}
采用折半查找算法查找关键字为 21 的过程为:
当第三次做判断时,发现 mid 就是关键字 21 ,查找结束。
在做查找的过程中,如果 low 指针和 high 指针的中间位置在计算时位于两个关键字中间,即求得 mid 的位置不是整数,需要统一做取整操作。
#include <stdio.h>
#include <stdlib.h>
#define keyType int
typedef struct {
keyType key;//查找表中每个数据元素的值
//如果需要,还可以添加其他属性
}ElemType;
typedef struct{
ElemType *elem;//存放查找表中数据元素的数组
int length;//记录查找表中数据的总数量
}SSTable;
//创建查找表
void Create(SSTable **st,int length){
(*st)=(SSTable*)malloc(sizeof(SSTable));
(*st)->length=length;
(*st)->elem = (ElemType*)malloc((length+1)*sizeof(ElemType));
printf("输入表中的数据元素:\n");
//根据查找表中数据元素的总长度,在存储时,从数组下标为 1 的空间开始存储数据
for (int i=1; i<=length; i++) {
scanf("%d",&((*st)->elem[i].key));
}
}
//折半查找算法
int Search_Bin(SSTable *ST,keyType key){
int low=1;//初始状态 low 指针指向第一个关键字
int high=ST->length;//high 指向最后一个关键字
int mid;
while (low<=high) {
mid=(low+high)/2;//int 本身为整形,所以,mid 每次为取整的整数
if (ST->elem[mid].key==key)//如果 mid 指向的同要查找的相等,返回 mid 所指向的位置
{
return mid;
}else if(ST->elem[mid].key>key)//如果mid指向的关键字较大,则更新 high 指针的位置
{
high=mid-1;
}
//反之,则更新 low 指针的位置
else{
low=mid+1;
}
}
return 0;
}
int main(int argc, const char * argv[]) {
SSTable *st;
Create(&st, 11);
getchar();
printf("请输入查找数据的关键字:\n");
int key;
scanf("%d",&key);
int location=Search_Bin(st, key);
//如果返回值为 0,则证明查找表中未查到 key 值,
if (location==0) {
printf("查找表中无该元素");
}else{
printf("数据在查找表中的位置为:%d",location);
}
return 0;
}
折半查找的运行过程可以用二叉树来描述,这棵树通常称为“判定树”
折半查找的平均查找长度为:ASL = log2(n+1) – 1
。( n 个结点的判定树)
分块查找(块间有序,块内无序,块间折半,块内线性):
① 对索引表使用折半查找法(因为索引表是有序表)
② 确定了待查关键字所在的子表后,在子表内采用顺序查找法(因为各子表内部是无序表);
7.2:动态查找(在查找表中做查找操作的同时进行插入/修改/删除数据的操作,称此类表为动态查找表)
二叉排序树查找(见二叉树的内容)、
平衡二叉树查找(平衡二叉树属于二叉排序树,针对二叉排序树有时极不平衡导致效率低下问题(见二叉树的内容))、
B-/B+树查找(前三者均基于树结构)
7.3:哈希表(也叫散列表)查找
若关键字为k,则其值存放在f(k)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为散列函数,按这个思想建立的表为散列表
哈希函数的构造方法:
直接定址法:Hash(key) = a•key + b (a、b为常数) —— 以关键码key的某个线性函数值为哈希地址
优点:不会产生冲突。缺点:要占用连续地址空间,空间效率低。
除留余数法:Hash(key) = key mod p(p是一个整数) —— 以关键码除以p的余数作为哈希地址
方法:若设计的哈希表长为m,则一般取 p≤m 且为质数
乘余取整法:Hash(key) = B * (A * key mod 1)(A、B均为常数,且0<A<1,B为整数) —— 以关键码key乘以A,取其小数部分,然后再放大B倍并取整作为哈希地址
数字分析法:例如:H(81346532)=43,H(81301367)=06,取第四位和第七位作为地址
平方取中法:例如:2589的平方值为6702921,可以取中间的029为地址
处理冲突:
对不同的关键字可能得到同一散列地址,即k1≠k2,而f(k1)=f(k2),这种现象称为冲突
开放定址法(又叫再散列法)
hash地址=(key值+增量序列)% 表长,例子中增量序列使用线性探测法(di:1,2,3,4..m-1)
查找成功平均查找长度:(1+2+1+1+1+4+1+2+2)/9
例2:(36,15,22,40,63)
,哈希函数为:H(K)=K MOD 7。
K为关键字,用线性探测法再散列法处理冲突。
查找成功平均查找长度:(1+2+3 + 1 + 1)/5 = 1.6
查找失败平均查找长度:(5 + 4 + 3 + 2 + 1 + 2 + 1) / 7
如何理解查找失败:现在我的哈希表已经把这5个数据填进去了,当我取一个除了这5个数之外的数来查才会失败,比如我查数字7是否在这个表中,我通过哈希函数得到他应该在地址0里面,结果发现0地址已经被63占据了,那么我根据线性探测原则,我去1地址再找,发现被36占据了,以此类推,直到我找到地址4,发现里面没有数据,说明这个哈希表没有我要找的7,即我查询失败了,期间我查询比较了5次,所以地址0的查询失败次数为5次
再哈希法:同时构造多个不同的哈希函数Hi(i=1,2,3...k),当H1冲突时使用H2,直到不冲突。优点:数据不易产生聚集。
缺点:增加了计算时间
链地址法:将所有哈希地址为 i 的元素构成一个单链表,并将单链表的头指针存在哈希表的第 i 个单元中
平均查找长度ASL=(1 * 7 + 2 * 4 + 3 * 1) / 12 = 1.5 (查找成功平均长度=总查找成功次数 / 关键字个数)
建立公共溢出区:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素一律填入溢出表
装填因子 = (哈希表中的记录数) / (哈希表的长度)
8.内部排序
内部排序:整个排序过程不需要访问外存便能完成
外部排序:参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,需要借助外存
排序的稳定性:A和B的关键字值相等,若排序后A、B的先后次序保持不变,则称这种排序算法是稳定的,反之称为不稳定的。
8.1 插入类排序
直接插入排序:将第i
个记录插入到前面i-1
个已排好序的记录中(在查找插入位置时,采用的是顺序查找的方式)
public void insertSort(int[] a, int len) {
for (int i = 0; i < len; i++) {
int j = i - 1, temp = a[i];
while (j >= 0 && temp < a[j]) {
a[j + 1] = a[j];
j--;
}
a[j + 1] = temp;
}
}
public static void main(String[] args) {
int[] data = {48,62, 35,77,55,14 ,35,98};
insertSort(data,data.length);
System.out.println(Arrays.toString(data));
}
理解:
折半插入排序:插入时采用折半查找,插入第i个元素到r[i]到r[i-1]之间
#include <stdio.h>
void print(int a[], int n ,int i){
printf("%d:",i);
for(int j=0; j<8; j++){
printf("%d",a[j]);
}
printf("\n");
}
void BInsertSort(int a[],int size){
int i,j,low = 0,high = 0,mid;
int temp = 0;
for (i=1; i<size; i++) {
low=0;
high=i-1;
temp=a[i];
//采用折半查找法判断插入位置,最终变量 low 表示插入位置
while (low<=high) {
mid=(low+high)/2;
if (a[mid]>temp) {
high=mid-1;
}else{
low=mid+1;
}
}
//有序表中插入位置后的元素统一后移
for (j=i; j>low; j--) {
a[j]=a[j-1];
}
a[low]=temp;//插入元素
print(a, 8, i);
}
}
int main(){
int a[8] = {3,1,7,5,2,4,9,6};
BInsertSort(a, 8);
return 0;
}
希尔排序: 先将整个待排记录序列分割成若干子序列, 分别进行直接插入排序, 待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。
如何分割:将相隔某个增量dk的记录组成一个子序列,依次取d1=n/2,d2=d/2
,直到di=1为止
public void shellSort(int[] a, int len) {
for (int h = len / 2; h > 0; h /= 2) {
for (int i = h; i < len; i++) {
int temp = a[i];
int j = i - h;
for (; j >= 0; j -= h) {
if (temp < a[j]) {
a[j + h] = a[j];
} else {
break;
}
}
a[j + h] = temp;
}
}
}
public static void main(String[] args) {
int[] data = {46,55, 13,42,94,17 ,05,70};
shellSort(data, data.length);
System.out.println(Arrays.toString(data));
}
理解:
8.2:交换类排序
冒泡排序
{49,38,65,97,76,13,27,49}
如图 1 所示是对无序表的第一次起泡排序,最终将无序表中的最大值 97 找到并存储在表的最后一个位置
经过第二次冒泡,最终找到了除 97 之外的又一个最大值 76
然后继续执行,直到所有的数字都有序
#include <stdio.h>
//交换 a 和 b 的位置的函数
void swap(int *a, int *b);
int main()
{
int array[8] = {49,38,65,97,76,13,27,49};
int i, j;
int key;
//有多少记录,就需要多少次冒泡,当比较过程,所有记录都按照升序排列时,排序结束
for (i = 0; i < 8; i++){
key=0;//每次开始冒泡前,初始化 key 值为 0
//每次起泡从下标为 0 开始,到 8-i 结束
for (j = 0; j+1<8-i; j++){
if (array[j] > array[j+1]){
key=1;
swap(&array[j], &array[j+1]);
}
}
//如果 key 值为 0,表明表中记录排序完成
if (key==0) {
break;
}
}
for (i = 0; i < 8; i++){
printf("%d ", array[i]);
}
return 0;
}
void swap(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
快速排序
该方法的基本思想是:
- 先从数列中取出一个数作为基准数。
- 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
- 再对左右区间重复第二步,直到各区间只有一个数。
以一个数组作为示例,取区间第一个数为基准数(用X表示)。初始时,i = 0; j = 9; X = 72
i = 3; j = 7; X=72
i = j = 5 ;X=72
//快速排序
void quick_sort(int s[], int l, int r)
{
if (l < r)
{
//Swap(s[l], s[(l + r) / 2]); //将中间的这个数和第一个数交换 参见注1
int i = l, j = r, x = s[l];
while (i < j)
{
while(i < j && s[j] >= x) // 从右向左找第一个小于x的数
j--;
if(i < j)
s[i++] = s[j];
while(i < j && s[i] < x) // 从左向右找第一个大于等于x的数
i++;
if(i < j)
s[j--] = s[i];
}
s[i] = x;
quick_sort(s, l, i - 1); // 递归调用
quick_sort(s, i + 1, r);
}
}
8.3:选择类排序
简单选择排序
树形选择排序
堆排序
8.4:归并排序
8.5:基数排序
8.6:比较
元素的移动次数与关键字的初始排列次序无关的是:基数排序
。
元素的比较次数与初始序列无关是:选择排序
。
算法的时间复杂度与初始序列无关的是:直接选择排序
。
(1)简单排序法一般只用于 n 较小的情况(例如 n<30)。当序列中的记录“基本有序” 时,直接插入排序是最佳的排序方法。如果记录中的数据较多,则应采用移动次数较少 的简单选择排序法。
(2)快速排序、堆排序和归并排序的平均时间复杂度均为 O(nlogn),但实验结果表明,就平均时间性能而言,快速排序是所有排序方法中最好的。遗憾的是,快速排序在最坏情况下的时间性能为 O(n^2)。堆排序和归并排序的最坏时间复杂度仍为 O(nlogn),当 n 较大时,归并排序的时间性能优于堆排序,但它所需的辅助空间最多。
(3)可以将简单排序法与性能较好的排序方法结合使用。例如,在快速排序中,当划分 子区间的长度小于某值时,可以转而调用直接插入排序法;或者先将待排序序列划分成 若干子序列,分别进行直接插入排序,然后再利用归并排序法,将有序子序列合并成一 个完整的有序序列。
(4)基数排序的时间复杂度可以写成 O(dn)。因此,它最适用于 n 值很大而关键字的位 数 d 较小的序列。当 d 远小于 n 时,其时间复杂度接近 O(n)。
(5)从排序的稳定性上来看,在所有简单排序法中,简单选择排序是不稳定的,其他各 种简单排序法都是稳定的。然而,在那些时间性能较好的排序方法中,希尔排序、快速 排序、堆排序都是不稳定的,只有归并排序、基数排序是稳定的。
总结:
n比较小的时候,适合 插入排序和选择排序
基本有序的时候,适合 直接插入排序和冒泡排序
初始数据基本反序,则选用 选择排序
n很大但是关键字的位数较少时,适合 链式基数排序
n很大的时候,适合 快速排序 堆排序 归并排序
无序的时候,适合 快速排序
稳定的排序:冒泡排序 插入排序 归并排序 基数排序
复杂度是O(nlogn):快速排序 堆排序 归并排序
辅助空间(大 次大):归并排序 快速排序
好坏情况一样:简单选择(n^2),堆排序(nlogn),归并排序(nlogn)
最好是O(n)的:插入排序 冒泡排序
9补充
9.1时间复杂度
x=x+1; //时间复杂度为O(1),称为常数阶
for(i=1; i<=n; i++){ //时间复杂度为O(n),称为线性阶
x=x+1;
}
for(i=1; i<=n; i++){ //时间复杂度为O(n2),称为平方阶
for(j=1; j<=n; j++){
x=x+1;
}
}
for ( i=1; i < n; i++ ) {
y = y+1; //语句1
for ( j=0; j<=(2*n); j++ )
x++; //语句2
}
//语句1频度为(n-1);语句2频度为(n-1)x(2n+1)=2n2-n-1,因此时间复杂度T(n)=2n2-2=O(n2)。
i=1; //语句1
while (i<=n){
i=i*2; //语句2
}
//语句1频度为1;语句2频度需要进一步计算
假设while循环执行了x次,则当1*2*2*2...*2>n时结束循环,即2^x>n,则x=log2(n)
所以时间复杂度T(n)=log2(n)+1=O(logn)
常用的时间复杂度按照耗费的时间从小到大依次是:
O(1)<O(logn)<O(n)<O(nlogn)<O(n²)<O(n³)<O(2ⁿ)<O(n!)
9.2空间复杂度
定义:指算法在计算机内执行时所需存储空间的度量
数据结构的基本运算:修改、插入、删除、查找、排序、判空