您的位置:首页 > 理论基础 > 数据结构算法

数据结构复习-图、查找、排序

2015-03-24 08:45 295 查看
//查找/检索算法:
//平均比较/查找次数:ASL=pi*ci  i:1~n ci是找第i个记录需要进行比较的次数
//静态查找:顺序查找O(N)、二分查找O(log2 N)、分块查找
//动态查找:二叉排序树、平衡二叉树

//排序算法: 以从小到大为例
//基于选择:选择排序 O(N^2)、堆排序 O(N*log2 N)
//基于交换:冒泡排序 O(N^2)、快速排序 O(N*log2 N)
//基于比较:插入排序 O(N^2)、希尔排序
//其他:合并排序 O(N*log2 N)、基数排序 O(d(r+N)) -- d最长位数,r基数(即链表的个数),N数字个数

int LinearSearch(int arr[],int item)
{
}

int BinarySearch(int arr[],int n,int item)
{
int low = 0,high = n-1,mid;
while(low <= high)
{
mid = (low+high)/2;
if(arr[mid] == item)
{
return mid;
}
if(arr[mid] > item)
{
high = mid -1;
}
else
low = mid + 1;
}
return -1;
}

//性能介于顺序和二分法之间的,二分查找索引表,块内采取顺序查找
#define MAX 20
typedef struct {
int key; //关键字
int link; //对应块的起始下标
}IdxType;
typedef IdxType IDX[MAX];

int BlockSearch(IDX I, int m,int r[],int n,int item)
{
int low = 0,high = m-1,mid , i;
int b = n/m;   //每块的记录个数
while(low <= high)
{
mid = (low + high)/2;
if(I[mid].key >= k)
high = mid -1;
else
low = mid +1;
}

//查找完索引表后,顺序查找块中元素
i = I[high+1].link;
while(i <= I[high+1].link + b -1 && r[i].key!=k)
i ++;
if(i <= I[high +1].link +b-1)
return i;
else
return -1;
}

//内部查找的:
//二叉排序树:BST  左子树<根节点<右子树,按中序遍历得到的是递增序列
//任何节点插入二叉排序树时,均以叶子节点的形式插入。对一个序列依次插入所有节点。
//构造: O(N*log2 N)  查找:O(log2 N)
int BTreeSearch(int arr[], int item)
{
}

//平衡二叉树:AVL  每个节点的平衡因子(左子树高度-右子树高度)为0,-1,1   查找O(log2 N)
int BalTreeSearch(int arr[],int item)
{
}

//外部查找的:
//B-(多路平衡查找树) B+树

//哈希表查找
//哈希表:H(Ki)是关键字Ki所对应的对象存储在内存中的地址。关键字的取值区间一般远大于哈希地址的变化区间
//目标:使哈希地址均匀分布在连续内存上
//哈希函数构造方法:直接定址法(h(k) = k+c,浪费空间);除留余数法(h(k) = k mod p,最常使用,p是不大于关键字总数的素数)
//哈希冲突(同义词冲突)解决:开放定址法(以冲突的哈希地址为自变量,通过哈希冲突函数得到新的地址),拉链法(把所有的同义词用单链表连接
//起来,哈希表中存放链表头指针)
//开发定址法:记录规模小时使用,但是不能探查所有冲突,在冲突时做删除标记
//拉链法:无法确定表长,需要额外的空间,但是能真正的删除记录

//Sort
//选择排序,始终是O(N^2)
void Choose(int arr[],int n)
{
int tmp,k;
for (int i = 0; i < n-1 ; i++)
{
k = i;

for(int j = i+1; j < n ; j++)
{
if(arr[j] < arr[k])
{
k = j;
}
}
if(k!= i)
{
tmp = arr[i];
arr[j] = arr[k];
arr[k] = tmp;
}
}
}

//正序时只需要一遍循环 O(N),平均和最坏都是O(N^2)
void Booble(int arr[],int n)
{
int tmp;
for (int i = 0; i < n-1 ; i++)
{
for(int j = n-1; j > i ; j--)
{
if(arr[j] < arr[j-1])
{
tmp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = tmp;
}
}
}
}

//假设前i个数已经排好序了,把i+1~n的数插入进去
////正序时只需要一遍循环 O(N),平均和最坏都是O(N^2)
void Insert(int arr[],int n)
{
int tmp;
for(int i = 1;i < n;i++)
{
tmp = arr[i];
j = i-1;
//找到应该插入的位置
while(j >= 0 && tmp < arr[j])
{
arr[j+1] = arr[j];
j--;
}
arr[j+1] = tmp;
}
}

//二分法查找插入排序
void BinaInsert(int arr[],int n)
{
int i,j,low=0,high=n-1,mid;
int tmp;
for(int i = 1;i < n;i++)
{
tmp = arr[i];
//二分查找应该插入的位置
while(low <= high)
{
mid = (low + high)/2;
if(tmp < arr[mid])
{
high = mid-1;
}
else
low = mid+1;
}

for(j = i-1; j >= high+1;j--)
{
arr[j+1] = arr[j];

}
arr[high + 1] = tmp;
}
}

//希尔排序 O(N^1.3)
void ShellSort(int arr[],int n)
{
int i,j,gap = n/2,tmp;
while(gap>0)
{
for(i = gap; i <n ; i++)
{
//对间隔gap的元素进行直接插入排序
tmp = arr[i];
j = i-gap;
while(j >= 0 && tmp < arr[j])
{
arr[j+gap] = arr[j];
j = j-gap;
}
arr[j+gap] = tmp;
}
//减小增量
gap = gap/2;
}
}

//快速排序: 平均时间:O(N*log2 N) 平均空间:O(log2 N) --- 递归树的高度
//最坏时:正序或者反序所需的比较次数最多: O(N^2)  O(N)
void Quick(int arr[],int s,int t)
{
int i = s, j = t;
int tmp;
if(s < t)
{
tmp = arr[s];
while(i!=j)
{
while(j>i && arr[j] > tmp)
j --;
arr[i] = arr[j];
while(i<j && arr[i] < tmp)
i ++;
arr[j] = arr[i];
}
arr[i] = tmp;
Quick(arr,s,i-1);
Quick(arr,i+1,t);
}

}

//堆是完全二叉树,每次选择双亲节点归位,构造初始堆
void sift(int arr[],int low,int high)
{
int i = low,j = 2*i; //j是左孩子
int tmp = arr[i];  //保存双亲节点
while(j <= high)
{
if(j < high && arr[j] < arr[j+1])
{
j ++;
}
if(tmp < arr[j])
{
arr[i] = arr[j];
i = j;
j = 2*i;
}
else break;
}
arr[i] = tmp;
}
void Heap(int arr[],int n)
{
int i,tmp;
//建立初始堆
for(i = n/2;i>=1;i--)
sift(arr,i,n);
//进行n-1趟堆排序,每次元素个数-1
for(i = n; i >=2; i--)
{
tmp = arr[1];
arr[1] = arr[i];
arr[i] = tmp;
sift(arr,1,i-1);
}
}

//对两个有序表进行二路归并,自底向上的 把表看作多个长度为1的子表进行归并;自顶向下的先分解至子表长度为1,然后进行归并
void Merge(int arr[],int low,int mid,int high)
{
int *r;
int i = low,j = mid+1,k=0;
r = (int *)malloc(sizeof(int)*(high-low +1));
while(i <= mid&& j <= high)
{
if(arr[i] <= arr[j])
{
r[k] = arr[i];
i++; k++;
}
else
{
r[k] = arr[j];
j++; k++;
}
}
while(i<= mid)
{
r[k] = arr[i];
i++;k++;
}
while(j <= high)
{
r[k] = arr[j];
j ++; k++;
}
//把low~mid,mid+1~high归并完后复制回去
for(k = 0,i = low;i<=high;k++,i++)
{
arr[i] = r[i];
}
}
//O(N*log2 N)
void MergeSort(int arr[],int low,int high)
{
int mid;
if(low < high)
{
mid = (low + high)/2;
MergeSort(arr,low,mid);
MergeSort(arr,mid+1,high);
Merge(arr,low,mid,high);
}
}

void MergeS(int arr[],int n)
{
MergeSort(arr,0,n-1);
}

//分配:把元素插入对应的队列中  收集:把队列连成线性表  重复多遍
void Jishu(int arr[],int n)
{
}

查找:http://blog.csdn.net/jiuqiyuliang/article/details/24405965

八大排序:http://blog.csdn.net/jiuqiyuliang/article/details/25304009

==============================================================================================================================

以下转载:视觉感受7种常用排序算法:

1. 快速排序

介绍:

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n)
算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来,且在大部分真实世界的数据,可以决定设计的选择,减少所需时间的二次方项之可能性。

步骤:

从数列中挑出一个元素,称为 “基准”(pivot),
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

排序效果:






2. 归并排序

介绍:

归并排序(Merge sort,台湾译作:合并排序)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide
and Conquer)的一个非常典型的应用

步骤:

申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
设定两个指针,最初位置分别为两个已经排序序列的起始位置
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针达到序列尾
将另一序列剩下的所有元素直接复制到合并序列尾

排序效果:






3. 堆排序

介绍:

堆积排序(Heapsort)是指利用这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

步骤:

(比较复杂,自己上网查吧)

排序效果:






4. 选择排序

介绍:

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。以此类推,直到所有元素均排序完毕。

排序效果:






5. 冒泡排序

介绍:

冒泡排序(Bubble Sort,台湾译为:泡沫排序或气泡排序)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

步骤:

比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

排序效果:






6. 插入排序

介绍:

插入排序(Insertion Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

步骤:

从第一个元素开始,该元素可以认为已经被排序
取出下一个元素,在已经排序的元素序列中从后向前扫描
如果该元素(已排序)大于新元素,将该元素移到下一位置
重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
将新元素插入到该位置中
重复步骤2

排序效果:

(暂无)


7. 希尔排序

介绍:

希尔排序,也称递减增量排序算法,是插入排序的一种高速而稳定的改进版本。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

1、插入排序在对几乎已经排好序的数据操作时, 效率高, 即可以达到线性排序的效率

2、但插入排序一般来说是低效的, 因为插入排序每次只能将数据移动一位>

排序效果:





#include <stdio.h>
#include <stdlib.h>

//图的基本概念:
//有向图:入度,出度
//无向图:度
//图中所有顶点的度之和=边数*2
//完全图:每两个顶点间都有一条边,
//路径:从一个顶点到另一个顶点的顶点序列; 路径长度:路径上经过的边的数目
//无向图:任意两个顶点间都存在边,极大连通子图=连通分量
//无向图:任意两个顶点间都存在边,极大强连通图=强连通分量
//关节点:在删除该节点及其关联的边后,把图的一个连通分量分隔成多个连通分量; 重连通图:没有关节点的连通图
//网:即带权图
//简单路径/回路:路径上的顶点不重复

//图的邻接矩阵和邻接表存储方法
#define MAX 20

/*
邻接矩阵的特性:
1.无向图的邻接矩阵是对称矩阵,可以只存三角矩阵
2.不带权的有向图的邻接矩阵是稀疏矩阵,可以用三元组标识
3.无向图:邻接矩阵的第i行非0元素的个数是第i个顶点的度(对于有向图,是出度)
4.易于确定顶点间是否有边,但是确定总边数时,要按行和列进行扫描,复杂。
*/
typedef struct
{
int no;  //定点编号
char* info; //顶点其他信息
} VertexType;  //顶点类型

typedef struct
{
int edges[MAX][MAX];   	//邻接矩阵的边数组
int n,e;			  	//顶点数,边数
VertexType vxs[MAX];	//存放顶点信息
} MGraph;

//顺序分配与链式分配的结合,邻接表
/*
邻接表:
1.表示不唯一
2.n,e的无向图需要存储n个顶点和2*e个边,对于稀疏图,邻接表更省空间
3.第i个链表的长度是顶点i的度。
4.有向图中只存储了以顶点为起点的边,不宜找到指向该顶点的边,可以设计逆邻接表,保存指向该顶点的边。
*/
typedef struct ANode
{
int adjvex;				///该边的终点位置
struct ANode* nextarc;	//指向下一条边的指针
char* info;				//边的信息
} ArcNode;
typedef struct VNode
{
char* data;         //顶点信息
ArcNode* firstarc;  //指向第一条边
}VNode;

typedef VNode AdjList[MAX];
typedef struct
{
AdjList adjlist;  	//邻接表
int n,e; 			//顶点数,边数
} ALGraph;

/*邻接矩阵-->邻接表: O(N*N)
找出表中不为0的元素,然后为该点建立一个表节点并在邻接表对应得单链表中插入。
邻接表-->邻接矩阵: O(N+2E)
在邻接表中找相邻节点,找到后修改矩阵对应值。
N---顶点数   E---边数
*/
//图的遍历:BFS  DFS ,只有连通图的情况下才会完全访问
//应用:求简单路径和简单回路,可以采用深度优先的回溯方法。

//DFS:从初始顶点触发,对相邻的未被访问过的顶点进行访问,再从该顶点出发进行访问。
//邻接表:O(N+2E)    邻接矩阵:O(N*N)
void DFS(ALGraph* G,int v)
{
ArcNode* p;
//访问数组,从v开始访问
visited[v] = 1;
printf("%d ",v);
p = G->adjlist[v].firstarc;
while(p!=NULL)
{
if(visited[p->adjvex] == 0)
{
DFS(G,p->adjvex);
}
p = p->nextarc;
}
}

//BFS:层次遍历,广度搜索
void BFS(ALGraph* G,int v)
{
ArcNode* p;
//循环队列
int queue[MAX],front = 0,rear = 0;
int visited[MAX];
int w,i;
for(i = 0;i<G->n;i++)
{
visited[i] = 0;
}
printf("%2d ",v);
visited[v] = 1;

//v进队
rear = (rear+1)%MAX;
queue[rear] = v;
//队列不空则循环
while(front!=rear)
{
//出队一个元素
front = (front+1)%MAX;
w = queue[front];
p = G->adjlist[w].firstarc;
while(p!=NULL)
{
//若该顶点未被访问,则访问该顶点,然后进队
if(visited[p->adjvex] == 0)
{
printf("%2d ",p->adjvex);
visited[p->adjvex] = 1;
rear = (rear+1)%MAX;
queue[rear] = p ->adjvex;
}
p = p->nextarc;
}
}
printf("\n");
}

// 对于非连通图,需要对于每个顶点进行一次DFS/BFS

//生成树:极小连通子图,含有全部顶点,但是只有N-1条边。图中所有生成树中具有边上权值之和最小的为最小生成树。
//只能用已有的边来构造: Prim / Criskaer
//Prim:初始化U={v},以v到其他顶点的边为候选边,从候选边中选出最小权值的,其对应的顶点加入U中,然后修改候选边,若到新顶点的权值更小则更新其权值
//O(N*N)   N---顶点数  两重FOR循环

//Criskaer:把G中的边按权重从小到大排列,每次选择最小权值的边,若存在两个顶点所在的连通分量相同,若加入,则形成了回路,舍弃刚刚选取的边
//O(E*E)
//改进后O(E*log2 E):可以采用堆排序对边进行排序,利用并查集进行顶点合并,判断是否在一个连通分量。

//最短路径:无权图:经过顶点最少的路径; 有权图:经过边的权值之和最小的路径。
//求一个顶点到其他各顶点的最短路径: Dijkstra/Floyd
//Dijkstra:初始S={v},U中是其他顶点,选取顶点k使其到v距离最小,然后加入S中,可以借助k修改其他顶点到v的最短距离值,再重复选边,加点,改距离值。
//O(N*N)   可以利用该方法求最小生成树。

//Floyd: 求每对顶点间的最短路径
//产生一个递推矩阵,A(k)[i][j] 表示从i到j经过顶点编号不大于k的最短路径长度。
//用Path数组保存i到j中间节点编号不大于k的最短路径长度的前一个节点编号,-1标识没有中间节点
//A(-1)[i][j] = cost[i][j] ;  A(k+1)[i][j] = min{A(k)[i][j],A(k)[i][k+1] + A(k)[k+1][j]}

//有向图的拓扑排序:
//1.选择一个没有前驱的顶点,并输出
//2.删去该点,删去从此出发的有向边,重复直到不存在没有前驱的顶点为止。

//AOE网:描述工程的预计进度的带权有向图,只有一个源点和汇点。从源-》汇的最大路径长度成为关键路径,其上的活动称为关键活动。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: