数据 > 数据元素 > 数据项
数据元素与数据的关系:是集合的个体
数据项与数据的关系:是集合的子集
1.逻辑结构:数据元素之间的逻辑关系
2.物理结构/存储结构:数据元素机器关系在计算机内存中的表示(又称映像)
3.运算和实现:对数据元素可以试驾的操作以及这些操作在相应的存储结构上的实现
1.线性结构:
有且仅有一个开始和一个终端结点,并且所有结点都最多有一个直接前驱和一个直接后继
例如:线性表、栈、队列、串
2.非线性结构:
一个结点可能有多个直接前驱和直接后继
例如:树、图
1.集合结构:(数据元素之间)除了同属一个集合之外无任何其他关系
2.线性结构:一对一的线性关系
3.树形结构:一对多的层次关系
4.图状结构/网状结构:多对多的任意关系
1.顺序存储结构:
- 用一组连续的存储单元依次存储数据元素,数据元素之间的逻辑关系由元素的存储位置来表示
- C语言中用数组来实现顺序存储结构
2.链式存储结构
- 用一组任意的存储单元存储数据结构,数据元素之间的逻辑关系用指针来表示
- C语言中用指针(链表)来实现存储结构
3.索引存储结构
- 在存储结点信息的同时还建立附加的索引表
4.散列存储结构
- 根据结点的关键字直接计算出该结点的存储地址
1.数据类型
数据类型 = 值的集合 + 值集合上的一组操作
2.抽象数据类型(ADT)
抽象数据类型的形式化定义:
抽象数据类型可用D、S、P三元组表示
- D数据对象
- S是D上的关系集
- P是对D的基本操作集
定义格式:
ADT 抽象数据类型名{
数据对象:<数据对象的定义>
数据关系:<数据关系的定义>
基本操作:<基本操作的定义>
} ADT 抽象数据类型名
- 数据对象、数据关系的定义用伪代码描述
- 基本操作的定义格式为:
基本操作名(参数表)
初始条件:<初始条件描述>
操作结果:<操作结果描述>
#include
using namespace std;
typedef struct {
float realpart; //实部
float imagpart; //虚部
}Complex;
/*函数声明*/
void assign(Complex* A, float real, float imag); //赋值
//加减乘除
void add(Complex* C, Complex A, Complex B);
void minus(Complex* C, Complex A, Complex B);
void mutiply(Complex* C, Complex A, Complex B);
void divide(Complex* C, Complex A, Complex B);
/*函数实现*/
void assign(Complex* A, float real, float imag)
{
A->realpart = real;
A->imagpart = imag;
}
void add(Complex* C, Complex A, Complex B)
{
C->realpart = A.realpart + B.realpart;
C->imagpart = A.imagpart + B.imagpart;
}
void minus(Complex* C, Complex A, Complex B)
{
C->realpart = A.realpart - B.realpart;
C->imagpart = A.imagpart - B.imagpart;
}
void mutiply(Complex* C, Complex A, Complex B)
{
C->realpart = A.realpart * B.realpart - A.imagpart * B.imagpart;
C->imagpart = A.realpart * B.imagpart + A.imagpart * B.realpart;
}
void divide(Complex* C, Complex A, Complex B)
{
C->realpart = (A.realpart * B.realpart + A.imagpart * B.imagpart) / (B.realpart * B.realpart + B.imagpart * B.imagpart);
C->imagpart = (B.realpart * A.imagpart - B.imagpart * A.realpart) / (B.realpart * B.realpart + B.imagpart * B.imagpart);
}
程序 = 数据结构 + 算法
- 数据结构通过算法实现操作
- 算法根据数据结构设计程序
定理1.1:若 f(n) 是关于n的幂函数,则 T(n) = O(n的最高次幂)
忽略低次幂项和高次幂项的系数
c++ 链表基础知识
C/C++ 内存的动态申请与释放
线性表是具有相同特性的数据元素的一个有限序列
顺序存储结构存在的问题:
- 存储空间不灵活
- 运算的空间复杂度高
优点:
- 存储密度大
- 可以随机存取表中任一元素
缺点:
- 在插入、删除某一元素时,需要移动大量元素
- 浪费存储空间
- 是静态存储,数据元素的个数不能自由扩充
#pragma once
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define LOVERFLOW -2 //中已有OVERFLOW,因此换一下
#define LIST_INIT_SIZE 100 //初始大小为100,可按需修改
#define LISTINCREMENT 10 //空间分配增量,课按需修改
typedef int Status; //函数调用状态
#define ELEMTYPE_IS_INT //数据类型
//#define ELEMTYPE_IS_DOUBLE
//#define ELEMTYPE_IS_CHAR_ARRAY
//#define ELEMTYPE_IS_CHAR_P
//#define ELEMTYPE_IS_STRUCT_STUDENT
//#define ELEMTYPE_IS_STRUCT_STUDENT_P
#ifdef ELEMTYPE_IS_DOUBLE
typedef double ElemType;
#elif defined (ELEMTYPE_IS_CHAR_ARRAY)
typedef char ElemType[LISTINCREMENT];
#elif defined (ELEMTYPE_IS_CHAR_P)
typedef char* ElemType;
#elif defined (ELEMTYPE_IS_STRUCT_STUDENT)
typedef struct student {
int num;
char name[LISTINCREMENT];
char sex;
float score;
char addr[30];
}ElemType;
#elif defined (ELEMTYPE_IS_STRUCT_STUDENT_P)
typedef struct student {
int num;
char name[LISTINCREMENT];
char sex;
float score;
char addr[30];
}ET, * ElemType;
#else
typedef int ElemType;
#endif
typedef struct {
ElemType* elem;
int length;
int listsize;
}sqlist;
/*函数声明*/
Status InitList(sqlist* L); //构造一个空的线性表L
Status DestroyList(sqlist* L); //销毁线性表L
Status ClearList(sqlist* L); //将线性表L置为空表
Status ListEmpty(sqlist L); //若L为空表返回TURE,否则返回FALSE
int ListLength(sqlist L); //返回L中数据元素个数
Status Getelem(sqlist L, int i, ElemType* e); //用e返回L中第i个数据元素的值
int LocateElem(sqlist L, ElemType e, Status(*compare)(ElemType e1, ElemType e2)); //返回L中第一个与e满足关系compare()的数据元素的位序,若不存在,返回0
Status PriorElem(sqlist L, ElemType cur_e, ElemType* pre_e, Status(*compare)(ElemType e1, ElemType e2)); //用pre_e返回cur_e的前驱
Status NextElem(sqlist L, ElemType cur_e, ElemType* next_e, Status(*compare)(ElemType e1, ElemType e2)); //用next_e返回cur_e的后驱
Status ListInsert(sqlist* L, int i, ElemType e); //在L的第i个位置插入元素e
Status ListDelete(sqlist* L, int i, ElemType* e); //删除L的第i个元素,并用e返回其值
Status ListTraverse(sqlist L, Status(*visit)(ElemType e)); //依次对L的每个数据元素调用visit()函数
#include
#include //malloc//realloc函数
#include //strcpy/strcmp等函数
#include
#include "liner_list_sq.h" //形式定义
/*初始化线性表*/
Status InitList(sqlist* L)
{
L->elem = (ElemType*)malloc(LIST_INIT_SIZE * sizeof(ElemType));
if (!L->elem)
exit(LOVERFLOW);
L->length = 0;
L->listsize = LIST_INIT_SIZE;
return OK;
}
/*销毁线性表*/
Status DestroyList(sqlist* L)
{
/*未执行InitList,直接执行本函数可能出错,因为指针初值未定*/
/*当类型是char *,struct student*时,要先释放二次申请空间*/
#if defined (ELEMTYPE_IS_CHAR_P) || defined (ELEMTYPE_IS_STRUCT_STUDENT_P)
for (int i = 0; i < L->length; i++)
free(L->elem[i]);
#endif
if (L->elem)
free(L->elem);
L->length = L->listsize = 0;
return OK;
}
/*清除线性表*/
Status ClearList(sqlist* L)
{
/*当类型是char *,struct student*时,要先释放二次申请空间*/
#if defined (ELEMTYPE_IS_CHAR_P) || defined (ELEMTYPE_IS_STRUCT_STUDENT_P)
for (int i = 0; i < L->length; i++)
free(L->elem[i]);
#endif
L->length = 0;
return OK;
}
/*判断是否为空表*/
Status ListEmpty(sqlist L)
{
if (L.length == 0)
return TRUE;
else
return FALSE;
}
/*求表的长度*/
int ListLength(sqlist L)
{
return L.length;
}
/*取表中元素*/
Status Getelem(sqlist L, int i, ElemType* e)
{
if (i<1 || i>L.length)
return ERROR;
#if defined (ELEMTYPE_IS_CHAR_ARRAY) || defined (ELEMTYPE_IS_CHAR_P)
strcpy(*e, L.elem[i - 1]);
#elif defined (ELEMTYPE_IS_STRUCT_STUDENT)
memcpy(e, &(L.elem[i - 1]), sizeof(ElemType));
#elif defined (ELEMTYPE_IS_STRUCT_STUDENT_P)
memcpy(*e, L.elem[i - 1], sizeof(ET));
#else
* e = L.elem[i - 1];
#endif
return OK;
}
memcpy函数的使用:
void *memcpy(void *dest, const void *src, int n);
- 将从源地址开始的n个字节复制到目标地址中
- 整体内存拷贝,不论中间是否有尾零
- 内存理解同char型数组但无法保证尾零,因此不能用strcpy
/*查找符合指定条件的元素*/
int LocateElem(sqlist L, ElemType e, Status(*compare)(ElemType e1, ElemType e2))
{
ElemType* p = L.elem;
int i = 1;
while (i <= L.length && (*compare)(*p++, e) == FALSE)
i++;
return (i <= L.length) ? i : 0;
}
Status MyCompare(ElemType e1, ElemType e2)
{
#if defined (ELEMTYPE_IS_DOUBLE)
if (fabs(e1 - e2) < 1e-6)
#elif defined (ELEMTYPE_IS_CHAR_ARRAY) || defined (ELEMTYPE_IS_CHAR_P)
if (strcmp(e1, e2) == 0)
#elif defined (ELEMTYPE_IS_STRUCT_STUDENT)
if (e1.num == e2.num)
#elif defined (ELEMTYPE_IS_STRUCT_STUDENT_P)
if (e1->num == e2->num)
#else
if (e1 == e2)
#endif
return TRUE;
else
return FALSE;
}
/*查找符合指定条件的元素的前驱元素*/
Status PriorElem(sqlist L, ElemType cur_e, ElemType* pre_e, Status(*compare)(ElemType e1, ElemType e2))
{
ElemType* p = L.elem;
int i = 1;
while (i <= L.length && (*compare)(*p, cur_e) == FALSE)
{
i++;
p++;
}
if (i == 1 || i > L.length)
return ERROR;
#if defined (ELEMTYPE_IS_CHAR_ARRAY) || defined (ELEMTYPE_IS_CHAR_P)
strcpy(*pre_e, *--p);
#elif defined (ELEMTYPE_IS_STRUCT_STUDENT)
memcpy(pre_e, --p, sizeof(ElemType));
#elif defined (ELEMTYPE_IS_STRUCT_STUDENT_P)
memcpy(*pre_e, *--p, sizeof(ET));
#else
* pre_e = *--p;
#endif
return OK;
}
/*查找符合指定条件的元素的后驱元素*/
Status NextElem(sqlist L, ElemType cur_e, ElemType* next_e, Status(*compare)(ElemType e1, ElemType e2))
{
ElemType* p = L.elem;
int i = 1;
while (i <= L.length && (*compare)(*p, cur_e) == FALSE)
{
i++;
p++;
}
if (i >= L.length)
return ERROR;
#if defined (ELEMTYPE_IS_CHAR_ARRAY) || defined (ELEMTYPE_IS_CHAR_P)
strcpy(*next_e, *++p);
#elif defined (ELEMTYPE_IS_STRUCT_STUDENT)
memcpy(next_e, ++p, sizeof(ElemType));
#elif defined (ELEMTYPE_IS_STRUCT_STUDENT_P)
memcpy(*next_e, *++p, sizeof(ET));
#else
* next_e = *++p;
#endif
return OK;
}
/*插入元素*/
Status ListInsert(sqlist* L, int i, ElemType e)
{
ElemType* p, * q;
if (i < 1 || i > L->length + 1)
return ERROR;
/*若空间已满则扩大空间*/
if (L->length >= L->listsize)
{
L->elem = (ElemType*)realloc(L->elem, (L->listsize + LISTINCREMENT) * sizeof(ElemType));
if (!L->elem)
return LOVERFLOW;
L->listsize += LISTINCREMENT;
}
q = &(L->elem[i - 1]);
/*从最后一个开始到第i个元素一次往后移一格*/
for (p = &(L->elem[L->length - 1]); p >= q; --p)
#if defined ELEMTYPE_IS_CHAR_ARRAY
strcpy(*(p + 1), *p);
#elif defined ELEMTYPE_IS_STRUCT_STUDENT
memcpy(p + 1, p, sizeof(ElemType));
#else
* (p + 1) = *p;
#endif
/*插入新元素*/
#if defined ELEMTYPE_IS_CHAR_ARRAY
strcpy(*q, e);
#elif defined ELEMTYPE_IS_CHAR_P
L->elem[i - 1] = (ElemType)malloc((strlen(e) + 1) * sizeof(char));
if (!L->elem[i - 1])
return LOVERFLOW
strcpy(*q, e);
#elif defined ELEMTYPE_IS_STRUCT_STUDENT
memcpy(q, &e, sizeof(ElemType));
#elif defined ELEMTYPE_IS_STRUCT_STUDENT_P
L->elem[i - 1] = (ElemType)malloc(sizeof(ET));
if (!L.elem[i - 1])
return LOVERFLOW
memcpy(*q, e, sizeof(ET));
#else
* q = e;
#endif
L->length++;
return OK;
}
/*删除元素并返回其值*/
Status ListDelete(sqlist* L, int i, ElemType* e)
{
ElemType* p, * q;
if (i < 1 || i > L->length)
return ERROR;
p = &(L->elem[i - 1]); //指向第i个元素
/*取第i个元素的值放入i中*/
#if defined (ELEMTYPE_IS_CHAR_ARRAY) || defined (ELEMTYPE_IS_STRUCT_STUDENT)
strcpy(*e, *p);
#elif defined (ELEMTYPE_IS_STRUCT_STUDENT)
memcpy(e, p, sizeof(ElemType));
#elif defined (ELEMTYPE_IS_STRUCT_STUDENT_P)
memcpy(*e, *p, sizeof(ET));
#else
* e = *p;
#endif
q = &(L->elem[L->length - 1]); //指向最后一个元素
/*两种情况需要释放空间*/
#if defined (ELEMTYPE_IS_CHAR_P) || defined (ELEMTYPE_IS_STRUCT_STUDENT_P)
free(*p);
#endif
/*从第i+1到最后,依次前移一格*/
for (++p; p <= q; ++p) {
#if defined ELEMTYPE_IS_CHAR_ARRAY
strcpy(*(p - 1), *p);
#elif defined ELEMTYPE_IS_STRUCT_STUDENT
memcpy((p - 1), p, sizeof(ElemType));
#else
* (p - 1) = *p;
#endif
}
L->length--; //长度-1
return OK;
}
/*遍历线性表*/
Status ListTraverse(sqlist L, Status(*visit)(ElemType e))
{
extern int line_count; //main中定义的换行计数器
ElemType* p = L.elem;
int i = 1;
line_count = 0; //计数器恢复初始值
while (i <= L.length && (*visit)(*p++) == TRUE)
i++;
if (i <= L.length)
return ERROR;
printf("\n");
return OK;
}
Status MyVisit(ElemType e)
{
#if defined (ELEMTYPE_IS_DOUBLE)
printf("%5.1f->", e);
#elif defined (ELEMTYPE_IS_CHAR_ARRAY) || defined (ELEMTYPE_IS_CHAR_P)
printf("%s->", e);
#elif defined (ELEMTYPE_IS_STRUCT_STUDENT)
printf("%d-%s-%c-%f-%s->", e.num, e.name, e.sex, e.score, e.addr);
#elif defined (ELEMTYPE_IS_STRUCT_STUDENT_P)
printf("%3d->", e);
#else
printf("%3d->", e);
#endif
if ((++line_count) % 10 == 0)
printf("\n");
return OK;
}
头结点的好处:
- 便于首元结点的处理
- 便于空表和非空表的统一处理
头结点的数据域可以为空,也可与存放线性表长度等信息,但此节点不能计入链表长度值
s->prior = p->prior;
p->prior->next = s;
s->next = p;
p->prior = s;
p->prior = s 必须在 p->prior->next = s 之后,其他顺序随意
p->prior->next = p->next;
p->next->prior = p->prior;
void union_sq(List &La,List Lb)
{
La_len = ListLength(La);
Lb_len = ListLength(Lb);
for (i = 1; i <= Lb_len; i++)
{
GetElem(Lb, i, e);
if(!LocateElem(La, e, equal))
ListInsert(La, ++La_len, e);
}
}
该算法的时间复杂度:O(ListLenth(La) * ListLengrh(Lb))
void MergeList_sq(List La,List Lb, SqList &Lc)
{
pa = La.elem; //指向La的首元素
pb = Lb.elem; //指向Lb的首元素
Lc.listsize = Lc.length = La.length + Lb.length;
pc = Lc.elem = (ElemType *)malloc(Lc.listsize * sizeof(ElemType));
if(!Lc.elem)
exit(OVERFLOW);
pa_last = La.elem + La.length - 1; //指向La的尾元素
pb_last = Lb.elem + Lb.length - 1; //指向Lb的尾元素
while(pa <= pa_last && pb <= pb_last)
{
if(*pa <= *pb)
*pc++ = *pa++;
else
*pc++ = *pb++;
}
while(pa <= pa_last) //若La还有剩余元素,插入Lc中
*pc++ = *pa++;
while(pb <= pb_last) //若Lb还有剩余元素,插入Lc中
*pc++ = *pb++;
}
该算法时间复杂度:O(ListLenth(La) + ListLengrh(Lb))
该算法空间复杂度:O(ListLenth(La) + ListLengrh(Lb))
void MergeList_L(List &La,List &Lb, SqList &Lc)
{
ElemType *pa = La->next;
ElemType *pb = Lb->next;
ElemType *pc = Lc = La;
while(pa && pb)
{
if(pa->data <= pb_data)
{
pc->next = pa;
pc = pa;
pa = pa->next;
}
else
{
pc->next = pb;
pc = pb;
pb = pb->next;
}
pc->next = pa?pb:pb; //插入剩余段
delete Lb;
}
该算法时间复杂度:O(ListLenth(La) + ListLengrh(Lb))
该算法空间复杂度:O(1)
typedef struct PNode{
float coef; //系数
int expn; //指数
struct PNode *next; //指针域
}PNode,*Polynomial;
void CreatPolyn(Polynomial &P, int n)
{
PNode* s, *pre, *q;
P = new PNode;
P->next = NULL;
for(int i = 1; i <= n; i++)
{
PNode* s = new PNode;
cin >> s-> coef >> s->expn;
pre = P;
q = P->next;
while(q && q->expn < s->expn)
{
pre = q;
q = q->next;
}
s->next = q;
pre->next = s;
}
}
struct Book{
char id[20]; //ISBN
char name[50]; //书名
int price; //价格
};
typedef struct{ //顺序表
Book *elem;
int length;
}SqList;
typdef struct LNnode{ //链表
Book data;
struct LNode *next;
}Lnode,*LinkList;
栈(LIFO):后进先出
队列(FIFO):先进先出
#define MAXSIZE 100
typedef struct{
SElemType *base; //栈底指针
SElemType *top; //栈顶指针
int stacksize; //栈可用最大容量
}SqStack;
//顺序栈的初始化
Status InitStack(Sqstack &S){
S.base =(SElemType*)malloc(MAXSIZE * sizeof(SElemType));
if(!S.base) //存储分配失败
exit(OVERFLOW);
S.top = S.base;
S.stacksize = MAXSIZE;
return OK;
}
//判断顺序栈是否为空
Status StackEmpty(SqStack S){
if(S.top == S.base)
return TURE;
else
return FALSE;
}
//求顺序栈长度
int StackLength(SqStack S){
return S.top - S.base;
}
//清空顺序栈
Status ClearStack(SqStack &S){
if(S.base)
S.top = S.base;
retuen OK;
}
//销毁顺序栈
Status DestroyStack(SqStack &S){
if(S.base){
free(S.base);
S.stacksize = 0;
S.base = S.top = NULL;
}
return OK;
}
//入栈
Status Push(SqStack &S, SElemType e){
if(S.top - S.base >= S.stacksize)//栈满,追加存储空间
{
S.base = (SElemType*)realloc(S.base,(S.stacksize + STACKINCREMENT) * sizeof(SElemType));
if(!S.base)
exit(OVERFLOW);
s.top = s.base + S.stacksize;
S.stacksize += STACKINCREMENT;
}
*S.top++ = e;
return OK;
}
//出栈
Status Pop(SqStack &S, SElemType &e){
if(S.top == S.base) //空栈
return ERROR;
e = *--S.top;
return OK;
}
typedef struct StackNode{
SElemType data;
struct StackNode *next;
}StackNode, *LinkStack;
//链栈的初始化
Status InitStack(LinkStack &S){
S = NULL;
return OK;
}
//入栈
Status Push(LinkStack &S, SElemType e){
p = (StackNode*)malloc(sizeof(StackNode));
p->data = e;
p->next = S;
S = p;
return OK;
}
//出栈
Status Pop(LinkStack &S, SElemType &e){
if(S == NULL)
return ERROR;
e = S->data;
p = S;
S = S->next;
free(p);
return Ok;
}
#define MAXQSIZE 100
typedef struct{
QElemType *base; //分配存储空间
int front; //头指针
int rear; //尾指针
}SqQueue;
Status InitQueue(SqQueue &Q){
//初始化队列
Q.base = (QElemtype*)malloc(MAXQSIZE * sizeof(QElemType));
if(!Q.base)
exit(OVERFLOW);
Q.front = Q.rear = 0;
return OK;
}
int QueueLength(SqQueue Q){
//队列长度
return ((Q.rear - Q.front + MAXQSIZE) % MAXQSIZE);
}
Status EnQueue(SqQueue &Q, QElemType e){
//插入元素
if((Q.rear + 1) % MAXSIZE == Q.front) //队满
return ERROR;
Q.base[Qrear] = e;
Q.rear = (Q.rear + 1) % MAXQSIZE;
return OK;
}
Status DeQueue(SqQueue &Q, QElemType &e){
//删除元素
if(Q.front == Q.rear)
return ERROR;
e = Q.base[Q.front];
Q.front = (Q.front + 1) % MAXQSIZE;
return OK;
}
#define MAXQSIZE 100 //最大队列长度
typedef struct QNode{
QElemType data;
struct QNode *next;
}QNode, *QueuePtr;
typedef struct{
QueuePtr front; //头指针
QueuePtr rear; //尾指针
}LinkQueue;
Status InitQueue(LinkQueue &Q){
//构造一个队列
Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode));
if(!Q.front)
exit(OVERFLOW);
Q.front->next = NULL;
return OK;
}
Status DestroyQueue(LinkQueue &Q){
//销毁队列
while(Q.front){
Q.rear = Q.front->next;
free(Q.front);
Q.front = Q.rear;
}
return OK;
}
Status EnQueue(LinkQueuue &Q, QElemType e){
//插入新元素
p = (QueuePtr)malloc(sizeof(QNode));
if(!p)
exit(OVERFLOW);
p->data = e;
p->next = NULL;
Q.rear->next = p;
Q.rear = p;
return OK;
}
Status DeQueue(LinkQueue &Q, QElemType &e){
//删除元素
if(Q.front == Q.rear)
return ERROR;
p = Q.front->next;
e = p->data;
Q.front->next = p->next;
if(Q.rear == p)
Q.rear = Q.front;
free(p);
return OK;
}
#define MAXLEN 255
typedef struct{
char ch[MAXLEN + 1]; //字符数组
int length; //串的当前长度
}SString;
#define CHUNKSIZE 80 //块的大小
typedef struct Chunk{
char ch[CHUNKSIZE];
struct Chunk *next;
}Chunk;
typedef struct{
Chunk *head, *tail; //头、尾指针
int curlen; //串当前长度
}LString;
int Index_BF(SString S, 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;
}
天勤公开课:KPM算法(易懂版)
KMP详解
void get_next(SString T, int next[]){
int i = 1, j = 0;
next[1] = 0;
while(i < T.length)
{
if(j == 0 || T.ch[i] == T.ch[j])
{
i++;
j++;
next[i] = j;
}
else
j = next[j];
}
}
int Index_KPM(SString S, SString T, int pos){
int i = pos, 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;
}
将next[i]的值对应的位的值与i的值进行比较,若相等,nextval[i]=nextval【next[i]】;若不相等,则nextval[i]=next[i]
void get_nextval(SString T, int nextval[]){
int i = 1, j = 0;
nextval[1] = 0;
while(i <T.length)
{
if(j == 0 || T.ch[i] == T.ch[j])
{
i++;
j++;
if(T.ch[i] != T.ch[j])
nextval[i] = j;
else
nextval[i] = nextval[j];
}
else
j = next[j];
}
}
aij = aji
只需存储上/下部分
以行序为主序将元素存储到一个一维数组 sa[n(n+1/2] 中。
位置:aij --> ∑(i - 1) + (j - 1) --> n(n-1)/2 + j - 1
对角线以下/上的元素都是同样的数
矩阵中大部分都是零元素(95%)
三元组法:
- 用三元组(i, j, aij)表示
- 通常加一个“总体”信息(总行数, 总列数, 非零元素个数)
优点:
- 非零元在表中按行序存储,便于进行按行顺序处理的运算
缺点:
- 不能随机存储,若按行号存取某一行中的非零元,则需从头开始进行查找
typedef struct{
int i, j; //非零元的行下标和;列下标
ElemType e;
}Triple;
typedef struct{
Triple data[MAXSIZE + 1]; //非零元三元组表,data[0]不用
int mu, nu, tu; //矩阵的行数、列数、非零元个数
}TSMatrix;
十字链表法:
- 每个非零元素用一个结点表示
- 结点除了(i, j, aij)外,还有right:用于连接同一行中的下个非零元素、down:用于连接同一列中的下个非零元素
优点:
- 能灵活的插入因运算产生的新的非零元素,删除因运算产生的新的零元素
树是n个结点的有限集
每个结点最多有两颗子树(度不大于2),且二叉树的子树有左右之分,其次序不能任意颠倒
- 满二叉树:
深度为k且有2k-1个结点的二叉树- 完全二叉树:
深度为k,由n个结点的二叉树,其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应(编号从上到下,从左到右)- 完全二叉树的特点:
1.叶子结点只能在层次最大的两层上出现
2.对任一结点,其右子树的最大层次为i,则其左子树的最大层次必为i或i+1
⌊⌋是向下取整符号
按二叉树的结点层次编号,依次存放二叉树中的数据元素,用"0"表示不存在的结点
适合完全二叉树
#define MAX_TREE_SIZE 100 //二叉树的最大结点数
typedef TElenmType sqBiTree[MAX_TREE_SIZE]; //0号单元存储根节点
SqBiTree bt;
typrdef struct BiTNode{
TElemType data;
struvt BiTNode *lchild, *rchild; //左右孩子指针
}BiTNode, *BiTree;
Status PreOrderTraverse(BiTree T){
if(T == NULL)
return OK;
else
{
visit(T); //访问根节点
PreOrderTraverse(T->lchild); //递归遍历左子树
PreOrderTraverse(T->rchild); //递归遍历右子树
}
}
Status InOrderTraverse(BiTree T){
if(T == NULL)
return OK;
else
{
InOrderTraverse(T->lchild); //递归遍历左子树
visit(T); //访问根节点
InOrderTraverse(T->rchild); //递归遍历右子树
}
}
Status PostOrderTraverse(BiTree T){
if(T == NULL)
return OK;
else
{
PostOrderTraverse(T->lchild); //递归遍历左子树
PostOrderTraverse(T->rchild); //递归遍历右子树
visit(T); //访问根节点
}
}
时间复杂度:O(n)
空间复杂度:O(n)
typedef struct{
BTNode data[MaxSize]; //存放队中元素
int fornt, rear; //队头、尾指针
}SqQueue; //循环队列类型
void LevelOrder(BTNode *b){
BTNode *p;
SqQueue *qu;
InitQueue(qu); //初始化队列
enQueue(qu, b); //根节点指针入队
while(!QueueEmpty(qu))
{
deQueue(qu, p); //出队结点p
printf("%c ", p->data);
if(p->lchild != NULL)
enQueue(qu, p->lchild); //有左孩子时将其入队
if(p->rchild != NULL)
enQueue(qu, p->rchild); //有右孩子时将其入队
}
}
按先序遍历序列建立二叉树和二叉链表
因为只有先序序列建立的树不唯一,所以要补充空结点(这里用’#'代替)
Status CreatBiTree(BiTree& T)
{
cin >> ch;
if (ch == '#')
T = NULL;
else
{
if(!(T = new BiTree))
exit(OVERFLOW);
T->data = ch; //生成根节点
CreateBiTree(T->lchild); //构建左子树
CreateBiTree(T->rchild); //构建右子树
}
return OK;
}
int Copt(BiTree T, BiTree& NewT)
{
if(T == NULL)
{
NewT = NULL;
return 0;
}
else
{
NewT = new BiNode;
NewT->data = T->data;
Copy(T->lchild, NewT->lchild);
Copy(T->rchild, NewT->rchild);
}
return 1;
}
int Depth(BiTree T)
{
if(T == NULL)
return 0;
else
{
int m, n;
m = Depth(T->lchild);
n = Depth(T->rchild);
m > n? return(m + 1):return(n + 1);
}
}
int NodeCount(BiTree T)
{
if(T == NULL)
return 0;
else
return NodeCount(T->lchild) + NCount(T->rchild) + 1;
}
int NodeCount(BiTree T)
{
if(T == NULL)
return 0;
if(T->lchild == NULL && T->rchild == NULL)
return 1;
else
return NodeCount(T->lchild) + NCount(T->rchild);
}
如果某个结点的左孩子为空,则将空的左孩子指针改为指向其前驱,右孩子同理
为了区分lchild和rchild是指向孩子的指针还是指向前驱/后驱的指针,对二叉链表中每个节点增设两个标志语ltag和rtag
typedef struct BiThrNode
{
int data;
int ltag, rtag;
struct BiTrNode *lchild, *rchild;
}BiThrNode, *BiThrTree;
森林:m(m >= 0)棵互不相交的树的集合
特点:找双亲容易,找孩子难
typedef struct PTNode
{
TElemType data;
int parent; //双亲位置
}PTNode;
typedef struct
{
PTNode nodes[MAX_TREE_SIZE];
int r; //根节点位置
int n; //结点个数
}PTree;
找孩子容易,找双亲难
把每个结点的孩子结点排列起来,看成线性表,用单链表存储,则n个结点有n个孩子链表(叶子结点为空表),而n个头指针又组成一个线性表,用顺序表(含n个元素的结构数组)存储
//孩子结点结构
typedef struct CTNode
{
int child;
struct CTNode *next;
}*ChildPtr;
//双亲结点结构
typedef struct
{
TElemType data;
ChildPtr firstchild; //孩子链表头指针
}CTBox;
//树结构
typedef struct
{
CTBox nodes[MAX_TREE_SIZE];
int r; //根节点位置
int n; //结点数
}CTree;
用二叉链表做树的存储结构,链表中每个结点的两个指针域分别指向其第一个孩子和下一个兄弟结点
typedef struct CSNode
{
ElemType data;
struct CSNode *firstchild, *nextsibling;
}CSNode, *CSTree;
用二叉链表做媒介,对应关系唯一
树变二叉树:兄弟相连留长子
二叉树变树:左孩右右连双亲,去掉原来右孩线
森林变树:树变二叉根相连
二叉树变森林:去掉全部右孩线,孤立二叉再还原
- 将二叉树中根节点与其右孩子连线,及沿右分支搜索到的所有右孩子间连线全部去掉,使之变成孤立的二叉树
- 将孤立的二叉树还原成树
将森林看作由三部分构成:
结点数目相同的二叉树中,完全二叉树是路径长度最短的二叉树。
完全二叉树是路径长度最短的数,但路径长度最短的数不一定是二叉树
Huffman树(最优二叉树):WPL最短的二叉树
- 满二叉树不一定是Huffman树
- Huffman树中权值越大的叶子离根越近
- 具有相同权结点的Huffman树不唯一
算法实现:用顺序存储结构——一维结构数组
typedef struct
{
int weight;
int parent;
int lch, rch;
}HTNode, *HuffmanTree;
void CreatHuffmanTree(HuffmanTree HT, int n)
{
int i;
if(n <= 1)
return;
int m = 2 * n = 1;
HT = new HTNode[m + 1]; //0号单元不用,HT[m]为根节点
for(i = 1; i <= m; i++) //初始化
{
HT[i].lch = 0;
HT[i].rch = 0;
HT[i].parent = 0;
}
for(i = 1; i < n; i++) //输入前n个元素的weight值
cin >> HT[i].weight;
for (i = n + 1; i <= m; i++) //合并产生n-1个结点——构造Huffman树
{
Select(HT, i - 1; s1; s2); //在HT[1..i-1]中选两个双亲域为0且权值最小的结点,并返回他们在HT中的序号s1、s2
HT[s1].parent = HT[s2].parent = i; //删除s1、s2
//设置左右孩子
HT[i].lch = s1;
HT[i].rch = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight; //设置权值
}
}
Huffman编码是最优前缀码
void CreatHuffmanCode(HuffmanTree HT, HuffmanCode &HC, int n)
{
HC = new char*[n + 1]; //分配n个字符编码的头指针矢量
char* cd = new char[n]; //分配存放临时存放编码的动态数组空间
cd[n - 1] = '\0';
//逐字符求Huffman编码
for(i = 1; i <= n; i++)
{
int start = n - 1;
int c = i;
int f = HT[i].parent;
while(f != 0)
{
start--; //回溯一次start向前指一个位置
if(HT[f].lchild == c)
cd[start] = '0';
else //是右孩子
cd[start] = '1';
//继续向上回溯
c = f;
f = HF[f].parent;
}//end of while
HC[i] = new char[n - start]; //为第i个字符串编码分配空间
strcpy(HC[i], &cd[start]); //将求得的编码复制
}//end of for
delete[]cd;
}
编码:
- 1.输入各字符及其权值
- 2.构造Huffman树
- 3.进行Huffman编码
- 4.查Huffman表得到各字符的Huffman编码
解码:
- 1.构造Huffman树
- 2.依次读入二进制码
- 3.读入0则走向左孩子,读入1则走向右孩子
- 4.一旦达到某叶子结点即可得到字符
稀疏图:有很少边/弧的图
稠密图
权:图中边/弧所具有的相关系数叫作权,表明从一个顶点到另一个顶点的距离或耗费
网:边/弧带权的图
邻接:有边/弧相连的两个顶点间的关系
存在(Vi, Vj),则称Vi和Vj互为邻接点
存在
关联(依附):边/弧与顶点间的关系
存在(Vi, Vj)/
顶点的度(TD):与该顶点相关联的边的数量
在有向图中,顶点的度 = 该顶点的入度 + 出度
入度(ID):以该顶点为终点的有向边的条数
出度(OD):以该顶点为起点的有向边的条数
路径:接续的边构成的顶点序列
路径长度:路径上边或弧的数目/权值之和
回路(环):第一个顶点和最后一个顶点相同的路径
强连通图:在有向图中,对任意两个顶点v、u都存在从v到u的路径,即为强连通图
极大连通子图:该子图是G的子图,将G的任何不在该子图中的顶点加入后,子图不再连通
连通分量:无向图的极大连通子图
强连通分量:有向图的极大连通子图
极小连通子图:该子图是G的子图,在该子图中删除任何一条边/弧,该子图不再连通
生成树:包含无向图G所有顶点的极小连通子图
生成森林:对非连通图,由各个连通分量生成的树的集合
特别:完全图的邻接矩阵中,对角元素为0,其余为1
#define Maxlnt 999999 //表示无穷大
#define MVNum 100 //最大顶点数
typedef char VertexType //顶点数据类型为字符型
typedef int ArcType //边的权值类型为int型
typedef struct
{
VertxType vexs[MVNum]; //顶点表
ArcType arcs[MVNum][MVNum]; //邻接矩阵
int vexnum, arcnum; //图当前点数和边数
}AMGraph; //Adjacency Matrix Graph
//用邻接矩阵表示法创建无向网
Status CreateUDN(AMGraph &G)
{
int i, j, k;
VertexType v1, v2;
ArcType w;
cin >> G.vexnum >> G.arcnum; //输入点数、边数
for(i = 0; i < G.vexnum; i++) //输入点的信息
cin >> G.vexs[i];
for(i = 0; i < G.vexnum; i++) //初始化邻接矩阵
for(j = 0; j < G.vexnum; j++)
G.arcs[i][j] = MAXlnt;
for(k = 0; k < G.arcnum; k++) //构建邻接矩阵
{
cin >> v1 >> v2 >> w; //输入一条边依附的顶点和边的权值
i = LocateVex(G, v1); //确定v1在G中的位置
j = LocateVEX(G, v2); //确定v2在G中的位置
G.arcs[i][j] = w; //邻接矩阵赋值
G.arcs[j][i] = G.arcs[i][j]; //无向图,反过来值相同
}
return OK;
}
优点:
- 直观,简单,好理解
- 方便查找任意一对顶点是否存在边
- 方便找任一顶点的所有邻接点
- 方便计算任一顶点的度
无向图:对应行或列非零元素个数
有向图:对应行非零元素的个数是出度,对应列非零元素的个数是入度缺点:
- 不便于增加或删除顶点
- 存稀疏图会有大量无效元素
- 统计边数时比较浪费时间
//顶点的结点结构
typedef struct VNode
{
VertexType data; //顶点信息
ArcNode* firstarc; //指向第一条依附该顶点的边的指针
}VNode, AdjList[MVNium]
//例如:VNode v[MVNum] 相当于 AdjList v
//弧/边的结点结构
typedef struct ArcNode
{
int adjvex; //该边所指向的顶点的位置
struct ArcNode* nextarc; //指向下一条边的指针
OtherInfo infi; //和边相关的信息
}ArcNode;
//图的结构定义
typedef struct
{
AdjList vertics; //顶点表
int vexnum, arcnum; //顶点数和边数
}ALGraph;
Status CreateYDG(ALGraph &G)
{
int i, j, k;
VertexType v1, v2;
cin >> G.vexnum >> G.arcnum; //输入顶点数和边数
for(i = 0; i < G,vexnum; i++)
{
cin >> G.vertices[i].data; //输入顶点值
G.vertices[i].firstarc = NULL; //初始化表头结点的指针域
}
for(k = 0; k < G.arcnum; k++)
{
cin >> v1 >> v2; //输入一条边依附的两个顶点
i = LocateVex(G, v1);
j = LocateVex(G, v2);
ArcNode *p1 = new ArcNode; //生成边结点
p1->adjvex = j;
p1->nextarc = G.vertices[i].firstarc; //头插法,将*p1插入顶点Vi的边表头部
G.vertices[i].firstarc = p1;
ArcNode *p2 = new ArcNode; //生成边结点
p2->adjvex = i;
p2->nextarc = G.vertices[j].firstarc; //头插法,将*p2插入顶点Vj的边表头部
G.vertices[j].firstarc = p2;
}
return OK;
}
解决不方便求结点的度的问题
可以看作为有向图的邻接表和逆邻接表结合起来形成的一种链表
有向图的每一条弧对挺十字链表中的一个弧结点,每个顶点对应着十字链表中的顶点结点
解决每条边都要存储两边的问题
void DFS(AMGraph G,int v)
{
int w;
//访问第v个结点(起点)
cout << vl << endl;
visited[v] = ture;
//依次检查邻接矩阵v所在的行
for(w = 0; w < G.vexnum; w++)
if(G.arcs[v][w] != 0 && !visited[w])
DFS(G, w);
}
邻接矩阵的时间复杂度:O(n2)
邻接表的时间复杂度:O(n+e)
void BFS(Graph G, int v)
{
int w;
//访问第v个结点(起点)
cout << vl << endl;
visited[v] = ture;
//初始化队列
InitQueue(Q);
EnQueue(Q, v); //v进队
while(!QueueEmpty(Q)) //队列非空
{
DeQueue(Q, u); //队头元素出队并置为u
for(w = FirstAdjVex(G, u); w >= 0; w = NextAdjVex(G, u, w))
if(!visited[w])
{
cout << w << endl;
visited[w] = true;
EnQueue(Q, w); //w进队
}
}
}
邻接矩阵的时间复杂度:O(n2)
邻接表的时间复杂度:O(n+e)
生成树:所有顶点由边连在一起,但没有回路
- 生成树的顶点个数与图的顶点个数相同
- 生成树是图的极小连通子图
- 生成树去掉一条边则非联通,加上一条边必然形成回路
- 生成树中任意两个顶点间路径唯一
- 一个有n个顶点的连通图的生成树有n-1条边
- 含n个顶点,n-1条边的图不一定是生成树
最小生成树(Minimun Spanning Tree):给定一个无向网,该网的所有生成树中使各边权值之和最小的树为最小生成树
时间复杂度:O(n2)
适合稠密图
时间复杂度:O(eloge) e为边数
适合稀疏图
AOV网:拓扑排序
用一个有向图表示一个工程的各子工程及其相互制约的关系,其中以顶点表示活动,弧表示活动之间的有限制约关系,称这种有向图为顶点表示活动的网
检测AOV网是否存在环的方法:
对有向图构造其顶点的拓扑有序序列,若网中所有顶点都在它的拓扑有序序列中,则该AOV网必无环
AOE网:关键路径
用一个有向图表示一个工程的各子工程及其相互制约的关系,其中以弧表示活动,顶点表示活动之间的开始或结束事件,称这种有向图为边表示活动的网
优点:
算法简单,逻辑次序无要求,不同存储结构均适用
缺点:
ASL太长,时间效率低
查找范围:
- 顺序表/线性表表示的静态查找表
表内元素之间无序
//数据元素类型定义
typedef struct{
KeyType key; //关键字域
... //其他域
}ElemType;
typedef struct{
ElemType *R; //表基址
int length; //表长
}SSTable; //Sequential Search Table
SSTable ST; //定义顺序表ST
//在ST中查找值为key的值
int Search_Seq(SSTable ST, KeyType key){
//此查找表为从1开始,0号位置为空,可按需更改
for (int i = 1; i <= ST.length; i++)
if(ST.R[i].key == key)
return i;
return 0;
//其他形式
/*for(i = ST.length; ST.R[i].key != key && i > 0; i--)
;
return i*/
}
改进:
把待查关键字key存入表头(哨兵/监视哨),从后往前逐个比较,可以免去查找过程中每一步都要检查是否查找完毕的步骤
int Search_Seq(SSTable ST, KeyType key){
ST.R[0].key = key;
for(i = ST.length; ST.R[i].key != key; i--)
;
return i
}
仅适用于有序表,且仅限于顺序存储结构
int Seaarch_Bin(SSTable ST, KeyType key)
{
int low = 1, high = ST.length; //初始化
while(low <= high)
{
int mid = (low + high) / 2;
if(ST.R[mid].key == key)
return mid;
else if(key < ST.R[mid].key)
high = mid - 1;
else
low = mid + 1;
}
return 0; //查找不到
}
- 若其左子树非空,则左子树上所有结点的值均小于根节点的值
- 若其右子树非空,则右子树上所有结点的值均大于根节点的值
- 其左右子树本身又各是一颗二叉排序树
- 二叉排序树可以是空树
typedef struct
{
KeyType key; //关键字
InfoType otherinfo; //其他数据域
}ElemType;
typedef struct BSTNode
{
ElemType data; //数据域
struct BSTNode *lchild, *rchild; //左右孩子指针
}BSTNode, *BSTree;
BSTree T; //定义二叉排序树T
BSTree SearchBST(BSTree T, KeyType key)
{
if((!T) || key == T->data.Tree)
return T;
else if(key < T->data.key)
return SearchBST(T->lchild, key); //在左子树继续查找
else
return SearchBST(T->rchild, key); //在右子树继续查找
}
平衡二叉树是具有以下性质的二叉排序树
- 左子树与右子树的高度之差的绝对值小于等于一
- 左子树和右子树也是平衡二叉排序树
- 平衡二叉树可以是空树
平衡因子 = 结点左子树高度 - 结点右子树高度
AVL的平衡因子只能是-1,0,1
降低高度,保持AVL的性质
基本思想:记录的存储位置与关键字之间存在对应关系(hash函数)
- 优点:查找效率快O(1)
- 缺点:空间效率低
构造好的散列函数:
- 所选函数尽可能简单,提高转换速度读
- 所选函数对关键码计算出的地址,应在散列地址集中致均匀分布,减少空间浪费
构造散列函数考虑的因素:
- 关键字长度
- 散列表的大小
- 关键字的分布情况
- 查找频率
制定一个好的解决冲突的方案:
- 查找时,如果从散列函数中计算出的地址中查不到关键码,应依据解决冲突的规则,有规律地查询其他相关单元
优点:
- 非同义词不会冲突,无“聚集”现象
- 适合表长不确定的情况(链表动态申请)
结论
- 散列表技术具有很好的平均性能,优于一些传统的技术
- 链地址法优于开地址法
- 除留余数法做散列函数优于其他类型函数
按数据存储介质:
- 内部排序:数据量不大、数据在内存,无需内外存交换数据
- 外部排序:数据量较大、数据在外存(文件排序),外存排序时,要将数据分批调入内存来排序,中间结果还要及时放入外存,比较复杂
按比较器个数:
- 串行排序:单处理机(同一时刻比较一对元素)
- 并行排序:多处理机(同一时刻比较多对元素)
按主要操作:
- 比较排序:插入排序、交换排序、选择排序、归并排序
- 基数排序:不比较元素大小,仅根据元素本身的取值确定其有序位置
按辅助空间:
- 原地排序:辅助空间用量为O(1),与参加排序的数据量大小无关
- 非原地排序:辅助空间用量超过O(1),与参加排序的数据量大小有关
按稳定性:
按自然性:
- 自然排序:输入数据越有序,排序的速度越快
- 非自然排序:输入数据越有序,排序的速度越慢
#define MAXSIZE 20 //记录最大个数
typedef int KetType; //关键字类型
//定义每个记录(数据元素)的结构
Typedef struct{
KeyType key; //关键字
InfoType otherinfo; //其他数据项
}RedType; //Record Type
//定义顺序表的结构
typedef struct{
RedType r[MAXSIZE + 1]; //存储顺序表的向量,r[0]一般做哨兵或缓冲区
int length; //顺序表长度
}SqList;
每步将一个待排序的对象,按其关键码大小,插入到前面已经排好序的一组对象的适当位置上,直到对象全部插入为止
void InsertSort(SqList &L){
int i, j;
for(i = 2; i < L.length; i++){
if(L.r[i].key < L.[r - 1].key){
L.r[0] = L.r[i]; //复制为哨兵
for(j = i - 1; L.r[0].key; j--){
L.r[j + 1] = L.[j]; //记录后移
}
L.r[j + 1] = L.r[0]; //插入到正确位置
}
}
}
时间复杂度:
- 最好情况:O(n)
- 最坏情况:O(n2)
- 平均情况:O(n2)
空间复杂度:O(1)
是一种稳定的排序方法
void BLinsertSort(SqList &L){
int i, j;
int low, high, mid;
for(i = 2; i <= L.length; i++){
L.r[0] = L.r[i]; //插入当前元素到哨兵位置
low = 1;
high = i - 1;
while(low <= high){ //用二分查找法查找插入位置
mid = (low + high) / 2;
if (L.r[0].key < L.r[mid].key)
high = mid - 1;
else
low = mid + 1;
}//循环结束,high+1为插入位置
for(j = i - 1; j >= high + 1; j--)
L.r[j + 1] = L.r[j]; //移动元素
L.r[high + 1] = L.r[0]; //插入正确位置
}
}
特点:
- 一次移动,移动位置比较大,跳跃式地接近排序后的最终位置
- 最后一次只需少量移动
- 增量必须递减,最后一次必须是1
- 增量序列应该是互质的
void ShellSort(SqList &L, int dlta[], int t){
//按增量序列dlta[0..t-1]对顺序表L作希尔排序
for(int k = 0; k < t; k++)
ShellInsert(L, dlta[k]); //一趟增量为dlta[k]的插入排序
}
void ShellInsert(SqList &L, int dk){
//对顺序表L进行一趟增量为dk的shell排序,dk为增长因子
int i, j;
for(i = dk + 1; i < L.length; i++){
if(L.r[i].key < L.[i - dk].key){
L.r[0] = L.r[i]; //复制为哨兵
for(j = i - dk; j > 0 && (L.r[0].key < L.r[j].key); j -= dk){
L.r[j + dk] = L.[j]; //记录后移
}
L.r[j + dk] = L.r[0]; //插入到正确位置
}
}
}
时间复杂度:
- 最好情况:O(n)
- 最坏情况:O(n2)
- 平均情况:O(n1.3)
空间复杂度:O(1)
是一种不稳定的排序方法
n个记录需要比较n-1次
第m次需要比较n-m次
void BubbleSort(SqList &L){
int i, j, m;
RedType temp; //交换时临时储存
bool flag = 1; //标记是否有交换
for(m = 1; m < L.length - 1 && flag; m++){
flag = 0;
for(j = 1; j <= L.length - m; j++){
if(L.r[j].key > L.[j + 1].key){ //交换
flag = 1;
temp = L.r[j];
L.r[j] = L.r[j + 1];
L.r[j + 1] = temp;
}
}
}
}
时间复杂度:
- 最好情况:O(n)
- 最坏情况:O(n2)
- 平均情况:O(n2)
空间复杂度:O(1)
是一种稳定的排序方法
void QSort(SqList &L, int low, int high){
if(low >= high)
return;
int pivotloc = Partition(L, low, high); //将L一份为二
QSort(L,low,pivotloc - 1); //对低子表递归排序
QSort(L, pivotloc + 1, high); //对高子表递归排序
}
int Partition(SqList &L, int low, int high){
L.r[0] = L.r[low]; //复制到哨兵位置
KeyType pivotkey = L.r[low].key;
while(low < high){
while(low < high && L.r[high].key >= pivotkey)
high--; //从后往前找小的
while(low < high && L.r[high].key <= pivotkey)
low++; //从前往后找大的
}
L.r[low] = L.r[0];
return low;
}
时间复杂度:
- 最好情况:O(nlogn)
- 最坏情况:O(n2)
- 平均情况:O(nlogn)
空间复杂度:O(nlogn)
是一种不稳定的排序方法
快速排序时,越乱越好,基本有序的不适合用快速排序
void SelectSort(SqList &K){
int i, j, k;
ElemType temp;
for(i = 1; i < L.length; i++){
k = i;
for(j = i + 1; j <= L.length; j++)
if(L.r[j].key < L.r[k].key)
k = j;
if(k != i){
temp = L.r[i];
L.r[i] = L.r[k];
L.r[k] = temp;
}
}
}
时间复杂度:
- 最好情况:O(n2)
- 最坏情况:O(n2)
- 平均情况:O(n2)
空间复杂度:O(1)
是一种不稳定的排序方法
筛选:
- 输出堆顶后,用堆中最后一个元素代替之
- 将根节点与左、右子树的根节点值比较,并与其中较小/大的交换
- 重复上述操作,直到叶子结点
void HeapAdjust(Elem R[], int s, int m){
//调整R[s]的关键字,使R[s..m]成为一个大根堆
rc = R[s];
for(j = 2 * s; j <= m; j *= 2){
if(j < m && R[j] < R[j + 1])
j++; //j为关键字较大的数据元素下标
if(rc >= R[j])
break;
R[s] = R[j];
s = j; //记录位置
}
R[s] = rc; //插入
}
for(int i = n / 2; i >= 1; i--)
HeadAdjust(R, i, n);
void HeapSort(Elen R[]){
int i;
for(int i = n / 2; i >= 1; i--) //建立初始堆
HeadAdjust(R, i, n);
for(i = n; i > 1; i--){
Swap(R[1], R[i]); //根与最后一个元素交换
HeapAdjust(R, 1, i - 1); //剩下的重新建堆
}
}
时间复杂度:O(nlogn)
空间复杂度:O(n2)
是一种不稳定的排序方法
将多个有序子序列归并成一个有序序列
需要进行⌈log2n⌉次
时间复杂度:O(nlogn)
空间复杂度:O(n)
是一种稳定的排序方法
基本思想:分配 + 收集
时间复杂度
- O(nlogn):快速排序、堆排序、归并排序
- O(n2):直接插入排序、冒泡排序、简单选择排序
- O(n):基数排序
- 当待排序序列为有序是:用直接插入排序、冒泡排序时间复杂度为O(n),而快速排序退化为O(n2)
- 简单选择排序、堆排序、归并排序的时间性能不随序列分布二改变
空间复杂度:
- O(1):所有简单排序(直接插入排序、冒泡排序、简单选择排序)和堆排序
- O(logn):快速排序
- O(n):归并排序