一、线性结构
以一元多项式及其运算为例:
f(x) = a0+a1x+…+anxn
1.顺序存储结构直接表示
多项式相加:直接相加
缺点:零项过多!占空间
2.顺序存储结构表示非零项
每个非零项涉及两个参数:系数和指数,所以可使用结构数组按指数大小有序存储表示
多项式相加:对应指数相加
3.链表结构存储非零项
链表中每个结点村塾多项式中的一个非零项,包括系数和指数两个数据域以及一个指针域
typedef struct PolyNode *Polynomial;
struct PolyNode
{
int coef;
int expon;
Polynomial link;
}
链表的插入(O(n)) 删除(O(n) 也可以O(1) 把下一个结点的值赋予该结点 删除下一个结点)
广义表:二元多项式
多重链表:图(邻接表)
二、树
1.概念:
结点的度:结点的子树个数
树的度:树种所有结点中最大的度
树的深度:树中所有结点中的最大层次是这棵树的深度 只有根节点 层次为1
2.二叉树性质与遍历:
完全二叉树:结点序号位置同满二叉树
对任何非空二叉树T,若n0表示叶节点个数 n1表示度为1的结点个数,n2表示度为2的非叶结点个数,那么根据二叉树边相等 存在no+n1+n2-1 = 2n2+n1 即n0 = n2+1。
先序遍历 中序遍历 后序遍历的递归写法与非递归写法
层序遍历(队列、堆栈)
已知中序遍历和另外一种 可唯一确定树
3.二叉搜索树:
BinTree Insert( BinTree BST, ElementType X )
{
if( !BST ){ /* 若原树为空,生成并返回一个结点的二叉搜索树 */
BST = (BinTree)malloc(sizeof(struct TNode));
BST->Data = X;
BST->Left = BST->Right = NULL;
}
else { /* 开始找要插入元素的位置 */
if( X < BST->Data )
BST->Left = Insert( BST->Left, X ); /*递归插入左子树*/
else if( X > BST->Data )
BST->Right = Insert( BST->Right, X ); /*递归插入右子树*/
/* else X已经存在,什么都不做 */
}
return BST;
}
BinTree Delete( BinTree BST, ElementType X )
{
Position Tmp;
if( !BST )
printf("要删除的元素未找到");
else {
if( X < BST->Data )
BST->Left = Delete( BST->Left, X ); /* 从左子树递归删除 */
else if( X > BST->Data )
BST->Right = Delete( BST->Right, X ); /* 从右子树递归删除 */
else { /* BST就是要删除的结点 */
/* 如果被删除结点有左右两个子结点 */
if( BST->Left && BST->Right ) {
/* 从右子树中找最小的元素填充删除结点 */
Tmp = FindMin( BST->Right );
BST->Data = Tmp->Data;
/* 从右子树中删除最小元素 */
BST->Right = Delete( BST->Right, BST->Data );
}
else { /* 被删除结点有一个或无子结点 */
Tmp = BST;
if( !BST->Left ) /* 只有右孩子或无子结点 */
BST = BST->Right;
else /* 只有左孩子 */
BST = BST->Left;
free( Tmp );
}
}
}
return BST;
}
4.平衡二叉树:
平衡因子:BF(T) = hl - hr;
平衡二叉树为空树或者任一结点左、右子树高度差的绝对值不超过1,即|BF(T)| <= 1
LR RR RL LL (麻烦结点相对于发现者的位置)
T是不平衡的发现者, 根据麻烦结点在发现者的位置 确定插入方式
// Root of AVL Tree
// LL RR LR RL 所有对AVL树的调整即可归结到这四种方法
#include <iostream>
using namespace std;
typedef struct TreeNode * Tree;
struct TreeNode
{
int value;
Tree left, right;
};
Tree maketree(int n); // 建立AVL 动态调整
Tree newnode(int v); // 申请新节点
Tree insert(Tree T, int v); //向树种插入元素
int height(Tree T); //计算一个树的高度
Tree LLrotation(Tree T);
Tree LRrotation(Tree T);
Tree RLrotation(Tree T);
Tree RRrotation(Tree T);
int main()
{
int n;
cin >> n;
Tree T;
T = maketree(n);
cout << T->value<<endl;
}
Tree maketree(int n)
{
int v;
cin >> v;
Tree T = newnode(v);
for(int i = 1; i < n; i++)
{
cin >> v;
T = insert(T,v);
}
return T;
}
Tree newnode(int v)
{
Tree T = new(struct TreeNode);
T->value = v;
T->left = T->right = nullptr;
return T;
}
Tree insert(Tree T, int v)
{
if(!T) T = newnode(v);
else
{
if(v < T->value)
{
T->left = insert(T->left,v);
if(height(T->left) - height(T->right) == 2)
{
if( v < T->left->value) // LL
T = LLrotation(T);
else
T = LRrotation(T);
}
}
else
{
T->right = insert(T->right,v);
if(height(T->right) - height(T->left) == 2)
{
if( v > T->right->value) // rr
T = RRrotation(T);
else
T = RLrotation(T);
}
}
}
return T;
}
Tree LLrotation(Tree T)
{
Tree a = T, b = T->left;//记录原来根节点,根节点的左节点
//Tree bl=b->left, br=b->right, ar=a->right;//记录三个需要旋转的树
a->left = b->right;
b->right = a;
return b;
}
Tree LRrotation(Tree T)
{
Tree a = T, b = T->left, c = b->right;//记录原来根节点,根节点的左节点
//Tree bl=b->left, br=b->right, ar=a->right;//记录三个需要旋转的树
b->right = c->left;
c->left = b;
a->left = c->right;
c->right = a;
return c;
}
Tree RRrotation(Tree T)
{
Tree a = T, b = T->right;//记录原来根节点,根节点的左节点
//Tree bl=b->left, br=b->right, ar=a->right;//记录三个需要旋转的树
a->right = b->left;
b->left = a;
return b;
}
Tree RLrotation(Tree T)
{
Tree a = T, b = T->right, c = b->left;//记录原来根节点,根节点的左节点
//Tree bl=b->left, br=b->right, ar=a->right;//记录三个需要旋转的树
b->left = c->right;
c->right = b;
a->right = c->left;
c->left = a;
return c;
}
int height(Tree T)
{
int hl,hr,max;
if(T)
{
hl = height(T->left);
hr = height(T->right);
max = hl > hr?hl:hr;
return max+1;
}
else
return 0;
}
5.堆:
1.用数组表示的完全二叉树
2.任一结点的关键字是其子树所有结点的最值
插入:向上过滤
typedef struct HNode *Heap; /* 堆的类型定义 */
struct HNode {
ElementType *Data; /* 存储元素的数组 */
int Size; /* 堆中当前元素个数 */
int Capacity; /* 堆的最大容量 */
};
typedef Heap MaxHeap; /* 最大堆 */
typedef Heap MinHeap; /* 最小堆 */
#define MAXDATA 1000 /* 该值应根据具体情况定义为大于堆中所有可能元素的值 */
MaxHeap CreateHeap( int MaxSize )
{ /* 创建容量为MaxSize的空的最大堆 */
MaxHeap H = (MaxHeap)malloc(sizeof(struct HNode));
// 数组
H->Data = (ElementType *)malloc((MaxSize+1)*sizeof(ElementType));
H->Size = 0;
H->Capacity = MaxSize;
H->Data[0] = MAXDATA; /* 定义"哨兵"为大于堆中所有可能元素的值*/
return H;
}
bool IsFull( MaxHeap H )
{
return (H->Size == H->Capacity);
}
bool Insert( MaxHeap H, ElementType X )
{ /* 将元素X插入最大堆H,其中H->Data[0]已经定义为哨兵 */
// 本质是将插入元素放到最后一个位置 然后自下向上过滤
int i;
if ( IsFull(H) ) {
printf("最大堆已满");
return false;
}
i = ++H->Size; /* i指向插入后堆中的最后一个元素的位置 */
for ( ; H->Data[i/2] < X; i/=2 )
H->Data[i] = H->Data[i/2]; /* 向下向上过滤X */
H->Data[i] = X; /* 将X插入 */
return true;
}
#define ERROR -1 /* 错误标识应根据具体情况定义为堆中不可能出现的元素值 */
bool IsEmpty( MaxHeap H )
{
return (H->Size == 0);
}
ElementType DeleteMax( MaxHeap H )
{ /* 从最大堆H中取出键值为最大的元素,并删除一个结点 */
/* 本质上是将堆的最后一个元素 放到堆的顶部 然后自上向下过滤*/
int Parent, Child;
ElementType MaxItem, X;
if ( IsEmpty(H) ) {
printf("最大堆已为空");
return ERROR;
}
MaxItem = H->Data[1]; /* 取出根结点存放的最大值 */
/* 用最大堆中最后一个元素从根结点开始向上过滤下层结点 */
X = H->Data[H->Size--]; /* 注意当前堆的规模要减小 */
// Parent*2<=H->Size 判别有无左儿子
for( Parent=1; Parent*2<=H->Size; Parent=Child ) {
Child = Parent * 2;
if( (Child!=H->Size) && (H->Data[Child]<H->Data[Child+1]) )
Child++; /* Child指向左右子结点的较大者 */
if( X >= H->Data[Child] ) break; /* 找到了合适位置 */
else /* 下滤X */
H->Data[Parent] = H->Data[Child];
}
H->Data[Parent] = X;
return MaxItem;
}
/*----------- 建造最大堆 -----------*/
void PercDown( MaxHeap H, int p )
{ /* 下滤:将H中以H->Data[p]为根的子堆调整为最大堆 */
int Parent, Child;
ElementType X;
X = H->Data[p]; /* 取出根结点存放的值 */
for( Parent=p; Parent*2<=H->Size; Parent=Child ) {
Child = Parent * 2;
if( (Child!=H->Size) && (H->Data[Child]<H->Data[Child+1]) )
Child++; /* Child指向左右子结点的较大者 */
if( X >= H->Data[Child] ) break; /* 找到了合适位置 */
else /* 下滤X */
H->Data[Parent] = H->Data[Child];
}
H->Data[Parent] = X;
}
void BuildHeap( MaxHeap H )
{ /* 调整H->Data[]中的元素,使满足最大堆的有序性 */
/* 这里假设所有H->Size个元素已经存在H->Data[]中 */
int i;
//思路:自下向上的 向下过滤 哈哈哈
/* 从最后一个结点的父节点开始,到根结点1 */
for( i = H->Size/2; i>0; i-- )
PercDown( H, i );
}
三、图
1.遍历
DFS(递归) BFS(队列)
2.最短路径
// 无权图的单源最短路算法, 每个边的权重都是1 非递减计算 类似于BFS
void Unweighted(Vertex S)
{
Enqueue(S,Q);
while(!IsEmpty(Q))
{
V = Dequeue(Q);
for ( V的每个邻接点W)
if(dist[W] == -1)
{
dist[w] = dist[V]+1; //S 到每个顶点W的最短路径
path[W] = V;//最短路径
Enqueue(W,Q);
}
}
}
//不能有负值圈 Dijkstra
// 有权图的单源最短路算法, 非递减计算 类似于BFS
void Dijkstra(Vertex S)
{
while(1)
{
V = 未收录顶点中dist最小者;
if V不存在
break;
collected[V] = true;
for(V的每个邻接点W)
if(collected[W] == false)
if(dist[V]+E < dist[W])
{
dist[W] = dist[V]+E;
path[W] = V;
}
}
}
void Floyd()
{
for(i = 0; i < N; i++)
for(j = 0; j < N; j++)
{
D[i][j] = G[i][j];
path[i][j] = -1;
}
for(k = 0; k < N; k++)
for(i = 0;i<N; i++)
for(j = 0; j < N; j++)
if(D[i][k] +D[k][j]<D[i][j])
{
D[i][j] = D[i][k]+D[k][j];
path[i][j] = k;
}
}
3.最小生成树
Prim :让一颗小树慢慢长大
void Prim()
{
MST = {s};
while(1)
{
V = 未收录顶点中dist最小的 (dist是顶点V离最小生成树的距离)
if(V不存在)
break;
将V收录进MST;dist[V] = 0;
for(V的每个邻接点W)
if(W未被收录)dist[W]!=0
if(E(v,w) < dist[W])
{
dist[W] = E(v,w);
parent[W] = V;
}
}
if(MST中的顶点不足V个)
Error;
}
Kruskal :将森林合并成树
void Kruskal(Graph G)
{
MST = {};
while(MST中不到|V| -1条边 && E中还有边)
{
从E中取一条权重最小的边E(v,w); //最小堆
将E(v,w)从E中删除;
if(E(v,w)不在MST中构成回路) //并查集
将E(v,w) 加入MST;
}
if(MST中的不足V-1个边)
Error;
}
4.拓扑排序
void Topsort( Graph G )
{
Queue Q;
Vertex V, W;
NodePtr ptr;
int counter = 0;
Q = CreateEmptyQueue(NumVertex);
for ( V=0; V<G->NumV; V++ )
if ( Indegree[V] == 0 )
Enqueue(V, Q);
while ( !IsEmpty(Q) ){
V = Dequeue( Q );
TopNum[V] = counter++;
for ( ptr=G->List[V]; ptr; ptr=ptr->Next) {
W = ptr->Vertex;
if ( --Indegree[W]== 0 )
Enqueue(W, Q);
}
}
if ( counter != NumVertex )
printf("ERROR: Graph has a cycle.\n");
DisposeQueue(Q);
}
四、排序
// 冒泡排序 泡泡泡泡 我的泡泡
void Bubble_sort(ElementType A[], int N)
{
for(P = N-1; P>= 0; P--)
{
flag = 0;
for(i = 0; i < P; i++) // P是最后一个未冒泡元素的位置 一趟冒泡
{
if(A[i] > A[i+1]) //稳定
{
Swap(A[i],A[i+1]);
flag = 1;
}
}
if(flag == 0) break;
}
}
性能比较:最好 O(N) 最坏O(N2)
// 插入排序
void Insertion_Sort(ElementType A[], int N)
{
for( P = 1; p < N; P++)
{
Tmp = A[P]; // 摸下一张牌
for(i = P; i > 0 && A[i-1] > Tmp; i--) // 往前面已排好的队里插
A[i] = A[i-1]; // 移出空位
A[i] = Tmp;// 新牌落位
}
}
最好顺序 O(N) 逆序 O(N^2)
// 时间复杂度下界
对于下标i<j,如果A[i] > A[j],则称(i,j)是一对逆序对
每次交换元素时,正好削去1个逆序对
所以插入排序 T(N,I) = O(N+I) 如果序列基本有序,则插入排序简单高效
定理:任何仅以交换相邻两元素来排序的算法,其平均时间复杂度未Ω(N^2)
优化点:交换相邻较远的两元素(可以大量减少逆序对)
// 希尔排序
void Shell_sort(ElementType A[], int N)
{
for (D = N/2; D > 0; D /= 2) //希尔增量序列
{
for(P = D; P<N; P++) //第0张牌在我手里 我从第D张牌开始摸
{
Tmp = A[P];
for(i = P; i >= D && A[i-D]>Tmp; i -= D)
A[i] = A[i-D];
A[i] = Tmp;
}
}
}
// 选择排序
void Selection_Sort(ElementType A[], int N)
{
for( i = 0; i < N; i++)
{
MinPosition = ScanForMin(A, i, N-1);
// 从 A[i]到A[N-1]中找最小元,并将其位置赋给MinPosition **这一步浪费时间 最小堆
Swap(A[i], A[MinPosition]);
// 将未排序部分的最小元换到有序部分的最后位置
}
}
// 堆排序
算法1: //需要额外存储空间
void Heap_Sort(ElementType A[], int N)
{
BuildHeap(A);
for (i = 0 ; i < N; i++)
TmpA[i] = DeleteMin(A);
for( i = 0; i < N; i++)
A[i] = TmpA[i];
}
算法2:
void PercDown( MaxHeap H, int p )
{
int Parent, Child;
ElementType X;
X = H->Data[p];
for( Parent=p; Parent*2<=H->Size; Parent=Child ) {
Child = Parent * 2;
if( (Child!=H->Size) && (H->Data[Child]<H->Data[Child+1]) )
Child++;
if( X >= H->Data[Child] ) break;
else
H->Data[Parent] = H->Data[Child];
}
H->Data[Parent] = X;
}
void Heap_Sort(ElementType A[], int N)
{
for( i = N/2; i >= 0; i--)
PercDown(A, i, N); //向下过滤 构造最大堆
for(i = N-1; i > 0; i--)
{
Swap(&A[0], &A[i]);// A[0]是最大的 与A[i]当前堆的最后一个交换
PercDown(A, 0, i); //因当前堆的最后一个位置已固定 所以仅需调节前面的节点成最大堆即可
}
}
// 归并排序
// 一个有序数组A[]的左部分喝右部分 合并
void Merge(ElementType A[], ElementType TmpA[], int L, int R, int RightEnd)
{
LeftEnd = R - 1; // 左边终点位置。假设左右两列挨着
Tmp = L; // 存放结果的数组初始位置
NumElements = RightEnd - L + 1; // 总元素数量
while( L <= LeftEnd && R <= RightEnd)
{
if(A[L] <= A[R]) TmpA[Tmp++] = A[L++];
else TmpA[Tmp++] = A[R++];
}
while(L <= LeftEnd)
TmpA[Tmp++] = A[L++];
while(R <= RightEnd)
TmpA[Tmp++] = A[R++];
for(i = 0; i < NumElements; i++, RightEnd--)
A[RightEnd] = TmpA[RightEnd];
}
void MSort(ElementType A[], ElementType TmpA[],int L, int RightEnd)
{
int Center;
if (L < RightEnd)
{
Center = ( L + RightEnd) /2;
MSort(A, TmpA, L, Center); //左边规整
MSort(A, TmpA, Center+1, RightEnd); //右边规整
Merge(A,TmpA,L,Center+1,RightEnd); //合并
}
}
void Merge_sort(ElemntType A[], int N)
{
ElementTyoe *TmpA;
TmpA = malloc (N * sizeof(ElementType));
if(TmpA != NULL)
{
MSort(A,TmpA,0,N-1);
free(TmpA);
}
}
//非递归写法
void Merge_pass(ElementType A[], ElementType TmpA[], int N, int length)
{ //length= 当前有序子列的长度
for(i = 0; i <= N-2*length; i+= 2*length)
Merge1(A,TmpA, i,i+length,i+2*length-1);//归并到TmpA;
if(i+length < N)
Mergel(A,Tmp,i,i+length,N-1);
else
for(j = i; j < N;j++) TmpA[j] = A[j];
}
void Merge_sort(ElemntType A[], int N)
{
int length=1;
ElementTyoe *TmpA;
TmpA = malloc (N * sizeof(ElementType));
if(TmpA != NULL)
{
while(length < N)
{
Merge_pass(A,TmpA,N,length);
length *= 2;
Merge_pass(TmpA,A,N,length);
length *= 2;
}
free(TmpA);
}
}
*/
void Quicksort(ElementType A[], int N)
{
pivot = 从A[]中选一个主元(基准);
A1 = {a<= pivot} ; a属于S(S为A中删除pivot的集合)
A2 = {a> pivot} ;
A[] = Quicksort(A1,N1)+pivot+Quicksort(A2,N2);
}
pivot 的选取可取头部、中部、尾部的中位数
ElementType Median3(ElementType A[], int left, int Right)
{
int center = (left + right ) / 2;
if( A[left] > A[center])
Swap(&A[left], &A[center]);
if(A[left] > A[right])
Swap(&A[left], &A[right]); // left是最小的
if(A[center] > A[right])
Swap(&A[center], &A[right]); // center是中部
Swap(&A[center], &A[right-1]); //将pivot藏到右边
// 由于 left必定比center小 而right必定比center大
// 所以直接排 left+1 right-2即可(pivot放在right-1)
return A[right-1];
}
void Quicksort(ElementType A[], int left ,int right)
{
pivot = Median(A,left,right);
i = left; j = right-1;
for(; ;)
{
while(A[++i] < Pivot) {}
while(A[--j] > Pivot) {}
if(i < j)
Swap(&A[i],&A[j]);
else break;
}
Swap(&A[i],&A[right-1]);
Quicksort(A,left,i-1);
Quicksort(A,i+1,right);
}
*/
/* 表排序 间接排序· 只对索引*/
/* 基数排序*/
//桶排序
void Bucket_Sort(ElementType A[], int N)
{
count[] 初始化;
while(读入1个学生成绩grade);
将该生插入count[grade]链表;
for(i = 0; i < M; i++)
{
if(count[i])
输出整个coount[i]链表
}
}
//基数排序
#define MaxDigit 4
#define Radix 10 // 十进制
typedef struct Node *PtrToNode;
struct Node {
int key;
PtrToNode next;
};
struct HeadNode {
PtrToNode head, tail;
};
typedef struct HeadNode Bucket[Radix];
// 得出X所在位数D上的数字
int GetDigit ( int X, int D )
{
int d, i;
for (i=1; i<=D; i++) {
d = X % Radix;
X /= Radix;
}
return d;
}
void LSDRadixSort( ElementType A[], int N )
{
int D, Di, i;
Bucket B;
PtrToNode tmp, p, List = NULL;
// 由于是十进制 所以放置10个桶 初始每个桶所存链表的头指针和尾指针都为空
for (i=0; i<Radix; i++)
B[i].head = B[i].tail = NULL;
// 将数组转为链表存储(倒置了) 最后list是该链表的头节点 其内容是A[N-1]
for (i=0; i<N; i++) {
tmp = (PtrToNode)malloc(sizeof(struct Node));
tmp->key = A[i];
tmp->next = List;
List = tmp;
}
for (D=1; D<=MaxDigit; D++) {
p = List;//顺序的确定依据这一句 原因list已将上一批次桶串接
while (p) {
Di = GetDigit(p->key, D);
tmp = p; p = p->next;
tmp->next = NULL;
// 如果该桶是空的 则该链表的头指针和尾指针都是tmp 否则再已有链表后追加
if (B[Di].head == NULL)
B[Di].head = B[Di].tail = tmp;
else {
B[Di].tail->next = tmp;
B[Di].tail = tmp;
}
}
// 在每一位的排序中(每一排桶 串接起来)
List = NULL;
for (Di=Radix-1; Di>=0; Di--) {
if (B[Di].head) {
B[Di].tail->next = List;
List = B[Di].head;
B[Di].head = B[Di].tail = NULL;
}
}
}
for (i=0; i<N; i++) {
tmp = List;
List = List->next;
A[i] = tmp->key;
free(tmp);
}
}
五、散列
散列函数构造:
1.直接定址法
2.除留余数法:h(key) = key mod p p一般为>=表长的素数
3.折叠法 平方取中法
冲突处理:
开放地址法:
线性探测、平方探测、双散列、再散列(加倍扩大散列表)
分离链接法