邻接矩阵
  对于一个又n个节点的图,邻接矩阵以一个n*n的二维数组a来描述图,对于不同的图,比如,有向图和无向图,带权图和无权图,a[i,j]表示的含义有所不同,但都是描述边的。
  邻接表
  邻接表组合使用数组和链表描述图,其中数组的每一个元素代表一个节点i,i由两部分组成,一部分代表节点的数据,另一部分为一个指向一链表,这个链表里存放着能从节点i出发能走到的所有节点。对于有向图和无向图会有不同的表示。邻接表一般要比领接矩阵更省空间,但它带来了求入度不便等问题。
  十字链表
  结合使用邻接表与逆邻接表,这种方式只能描述有向图。首先它也有一个数组,每个数据元素代表一个节点i,i由三部分组成,i在邻接表的基础上增加了一个指针,这个指针指向第一个以i为弧尾的及节点。这很好的解决了求入度的问题。
  邻接多重表
  邻接多重表主要,它主要用来表描述无向图,在邻接表或十字链表中,数组元素的指针域指向的链表元素其实代表了边,如果用邻接表来存无向图,会使得一条边对应的两个节点分别位于两条链中,当我需要删除一条边时,总是需要找到另一个表示这条边的边节点,再删除。所以有了邻接多重表,邻接多重表是只用一个边界点表示边,但是将它链接到两链表中(对,没有错,一个节点,同时存在于两个链表中)
  下面是上面四种描述的代码表示
  #ifndef __GRAPH_DEFINE_H__
  #define __GRAPH_DEFINE_H__
  #define INT_MAX            9999
  #define INFINITY        INT_MAX            // 大值
  #define MAX_VERTEX_NUM    20                // 大顶点个数
  #define VEX_FORM        "%c"            // 顶点输入格式
  #define INFO_FORM        "%d"            // 边信息输入格式
  typedef int InfoType;            // 弧相关信息类型
  typedef char VextexType;        // 顶点数据类型
  typedef int VRType;                // 顶点关系类型,对无权图,用0或者1表示是否相邻。对带权图,则是权值类型。
  typedef enum { DG,DN,UDG,UDN} GraphKind;// 图类型 {有向图,有向网,无向图,无向网}
  //////////////////////////////////////////////////////////////////////////
  //    邻接矩阵存储结构: 可存储任意类型图
  //////////////////////////////////////////////////////////////////////////
  typedef struct{
  VRType        Adj;
  InfoType    Info;                    // 该弧相关信息
  }ArcCell,AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
  typedef struct{
  char cVexs[MAX_VERTEX_NUM];            // 顶点向量
  AdjMatrix arcs;                        // 邻接矩阵
  int iVexNum,iArcNum;                // 图中当前顶点数和弧数
  GraphKind kind;                        // 图的种类标志
  }MGraph;
  //////////////////////////////////////////////////////////////////////////
  //    邻接表存储结构:    可存储任意类型的图
  //////////////////////////////////////////////////////////////////////////
  typedef struct ArcNode{
  int                iAdjvex;        // 该弧所指向的顶点位置
  struct ArcNode    *nextarc;        // 指向下一条弧的指针
  InfoType        Info;            // 该弧相关信息
  }ArcNode;
  typedef struct VNode{
  VextexType    cData;                // 顶点信息
  ArcNode        *firstarc;            // 指向第一条依附该顶点的弧的指针
  }VNode,AdjList[MAX_VERTEX_NUM];
  typedef struct {
  AdjList        vertices;
  int            iVexnum,iArcnum;    // 图的当前顶点个数和弧数
  GraphKind    Kind;                // 图的种类标志
  }ALGraph;
  //////////////////////////////////////////////////////////////////////////
  //    十字链表存储结构: 只存储有向图
  //////////////////////////////////////////////////////////////////////////
  typedef struct ArcBox{
  int                iTailVex,iHeadVex;        // 该弧的尾和头顶点的位置
  struct ArcBox    *hLink,*tLink;            // 分别为弧头相同和弧尾相同的链域
  InfoType        Info;                    // 该弧相关信息
  }ArcBox;
  typedef struct VexNode{
  VextexType        data;
  ArcBox            *firstIn,*firstOut;        // 分别指向了该顶点的第一条入弧和出弧
  }VexNode;
  typedef struct OLGraph{
  VexNode        xlist[MAX_VERTEX_NUM];        // 表头向量
  int            iVexNum,iArcNum;            // 有向图当前顶点数和弧数
  }OLGraph;
  //////////////////////////////////////////////////////////////////////////
  //    邻接多重表:    存储无向图
  //////////////////////////////////////////////////////////////////////////
  typedef enum {unvisited,visited}VisitIf;
  typedef struct EBox{
  VisitIf            mark;            // 访问标记
  int                iIVex,iJVex;    // 边依附的两个顶点的位置
  struct EBox        *iLink,*jLink;    // 分别指向依附这两个顶点的下一条边
  InfoType        Info;            // 该边信息指针
  }EBox;
  typedef struct VexBox{
  VextexType        data;
  EBox            *firstEdge;        // 指向第一条依附该顶点的边
  }VexBox;
  typedef struct {
  VexBox            adjmulist[MAX_VERTEX_NUM];
  int                iVexNum,iEdgeNum;    // 无向图当前顶点数和边数
  }
  #endif//__GRAPH_DEFINE_H__
  图相关操作
  对图的操作有创建,增删查等等,其中查是遍历,遍历分为深度优先搜索和广度优先搜索。
  深度优先搜索类似于树的钟旭遍历,即遇到未范围的节点,马上访问,修改标记数组,然后沿着这个节点继续访问,直到访问完,然后在回朔,转向未访问的分支。直到节点被访问完,如果是连通图,只要访问进行一次深度搜索,如果是非连通的,要搜索多次。
  广度优先搜索像金字塔从上向下的一层一层的搜索,广度优先搜索除了需要用标记数组记录状态以外,还需要用队列来将发现而未访问的节点记录下来。用队列是为了保证遍历顺序。
  下面是一些图相关的操作算法
  #include <stdio.h>
  #include "GraphDefine.h"
  #include "Define.h"
  #include <malloc.h>
  //////////////////////////////////////////////////////////////////////////
  //    邻接矩阵图的相关操作
  //////////////////////////////////////////////////////////////////////////
  Status CreateDG(MGraph &G);        // 构造有向图
  Status CreateDN(MGraph &G);        // 构造有向网
  Status CreateUDG(MGraph &G);    // 构造无向图
  Status CreateUDN(MGraph &G);    // 构造无向网
  int LocateVex(const MGraph &G,const VextexType &v);    // 获得顶点v在图中的位置
  Status CreateGraph(MGraph &G,VextexType v){
  // 采用数组(邻接矩阵)表示法,构造图G
  int iType;
  scanf("%d%*c",&iType);
  G.kind = (GraphKind)iType;
  switch(G.kind) {
  case DG: return CreateDG(G);        // 构造有向图
  case DN: return CreateDN(G);        // 构造有向网
  case UDG:return CreateUDG(G);        // 构造无向图
  case UDN:return CreateUDN(G);        // 构造无向网
  default:
  return ERROR;
  }
  }
  Status CreateUDN(MGraph &G){
  //  采用数组(邻接矩阵)表示法,构造网G
  int i,j,k;
  int IncInfo;
  scanf("%d %d %d",&G.iVexNum,&G.iArcNum,&IncInfo);
  for(i=0;i<G.iVexNum;++i)            // 构造顶点向量
  scanf("%c%*c",&G.cVexs[i]);
  for(i=0;i<G.iVexNum;i++){
  for(j=0;j<G.iVexNum;j++){        // 初始化邻接矩阵
  G.arcs[i][j].Adj = INFINITY;
  G.arcs[i][j].Info =NULL;       
  }
  }
  VextexType v1,v2;
  int w;
  for(k=0;k<G.iArcNum;k++){            // 构造邻接矩阵
  scanf("%c %c %d%*c",&v1,&v2,&w);
  i = LocateVex(G,v1),j = LocateVex(G,v2);    // 确定v1,v2在G中的位置
  G.arcs[i][j].Adj = w;            // 弧<v1,v2>的权值
  if(IncInfo) scanf(INFO_FORM,G.arcs[i][j].Info);
  G.arcs[j][i] = G.arcs[i][j];    // 置<v1,v2>对称弧<v2,v1>
  }
  return OK;
  }
  //////////////////////////////////////////////////////////////////////////
  //    十字链表图的相关操作
  //////////////////////////////////////////////////////////////////////////
  int LocateVex(const OLGraph &G,const VextexType &v);    // 获得顶点v在图中的位置
  Status CreateDG(OLGraph &G){
  // 采用十字链表存储表示,构造有向图 G(G.kind = DG)
  InfoType IncInfo;
  scanf("%d %d %d",G.iVexNum,G.iArcNum,&IncInfo);
  int i;
  for(i=0;i<G.iVexNum;i++){            // 构造表头向量
  scanf(VEX_FORM "%*c",G.xlist[i].data);            // 输入顶点值
  G.xlist[i].firstIn = G.xlist[i].firstOut = NULL;// 初始化指针
  }
  int k,j;
  VextexType    v1,v2;
  ArcBox * p;
  for(k=0;k<G.iArcNum;k++){            // 输入各弧并构造十字链表
  scanf(VEX_FORM VEX_FORM,&v1,&v2);            // 输入一条弧的始点和终点
  i = LocateVex(G,v1),j = LocateVex(G,v2);    // 确定V1和 V2在G中的位置
  p = (ArcBox*)malloc(sizeof(ArcBox));        // 假设有足够的空间
  // 对弧节点赋值
  p->iTailVex = v1,p->iHeadVex = v2;
  p->hLink = G.xlist[j].firstIn,p->tLink = G.xlist[i].firstOut;
  p->Info = NULL;
  G.xlist[j].firstIn = G.xlist[i].firstOut = p;    // 完成在入弧与出弧的链头的插入
  if(IncInfo) scanf(INFO_FORM,&p->Info);            //若含有相关信息,则输入
  }
  return OK;
  }
  //////////////////////////////////////////////////////////////////////////
  //    深度优先搜索
  //////////////////////////////////////////////////////////////////////////
  bool visited[MAX_VERTEX_NUM];
  Status(*VisitFunc)(int v);
  int FirstAdjVex(MGraph,int);
  int NextAdjVex(MGraph,int);
  void  DFSTeaverse(MGraph G,Status (*Visit)(int v)){
  int v;
  VisitFunc = Visit;
  for(v=0;v<G.iVexNum;v++) visited[v] = false;
  for(v=0;v<G.iVexNum;v++)
  if(!visited[v]) DFS(G,v);
  }
  void DFS(MGraph G,int v){
  visited[v] = true;
  VisitFunc(v);
  int w;
  for(w = FirstAdjVex(G,v); w>0 ; w = NextAdjVex(G,v))
  if(!visited[w]) DFS(G,w);
  }
  //////////////////////////////////////////////////////////////////////////
  //    广度优先搜索
  //////////////////////////////////////////////////////////////////////////
  //    队列相关函数
  void InitQueue(int []);
  void EnQueue(int []);
  bool QueueEmpty(int []);
  void DFSTeaverse(MGraph G,Status (*Visit)(int v)){
  int v;
  for (v=0;v<G.iVexNum;v++)
  {
  visited[v] = false;
  }
  int Q[MAX_VERTEX_NUM];
  InitQueue(Q);
  for(v=0;v<G.iVexNum;v++){
  if(!visited[v]){
  visited[v] = true;
  Visit(v);
  EnQueue(Q);
  while(!QueueEmpty(Q)){
  int u,w;
  DeQueue(Q,u);
  for( w = FirstAdjVex(G,u); w>=0 ; w = NextAdjVex(G,u))
  if(!visited[w]){
  visited[w] = true;
  Visit(w);
  EnQueue(Q,w);
  }
  }
  }
  }
  }
  与图相关的还有很多算法,比如求小生成树的prim算法和kruskal算法
  prim算法初始化一个s集合,始终挑选与s集合相连小的边连接的节点加到集合中,然后更新剩余节点到s的距离,直到所有的点添加进了s集合,prim算法代码如下
  #include <stdio.h>
  #include <string.h>
  #define MAXN 1024
  #define INF 0xeFFFFFFF
  int e[MAXN][MAXN];
  int low[MAXN];
  bool inSet[MAXN];
  int n;
  int prim(){
  int res=0,i,j,k;
  inSet[0] = true;
  memset(inSet,0,sizeof(inSet));
  for(i=0;i<n;i++){
  //现在所有点到S的距离是到0的距离
  low[i] = e[0][i];
  }
  for(i=1;i<n;i++){
  int minv = INF;
  int p = 0;
  //找出与集合s相连的小的边
  for(j=0;j<n;j++){
  if(!inSet[j] && low[j] < minv){
  minv = low[j],p = j;
  }
  }
  if(minv == INF) return -1;//非连通图
  //将顶点j加到S里面
  inSet[p] = true;
  //将短路径加到结果里
  res += low[p];
  //更新low数组
  for(k=0;k<n;k++){
  if(!inSet && low[p] > e[p][k]){
  low[p] = e[p][k];
  }
  }
  }
  return res;
  }
  int main(void){
  int i;
  scanf("%d",&n);
  for(i=0;i<n-1;i++){
  int x,y,w;
  scanf("%d%d%d",&x,&y,&w);
  e[x][y] = e[y][x] = w;
  }
  int res = prim();
  printf("%d ",res);
  return 0;
  }
  Kruskal算法不断选取小的边i,只要biani加进来不构成回路,则加入到边的集合e中来,直到加入的边能连接所有的顶点,则结束算法
  #include <stdio.h>
  #include <string.h>
  #include <stdlib.h>
  #define MAXN 1024
  #define INF 0xeFFFFFFF
  //定义边的结构
  typedef struct Ele{
  int x,y;    //边的端点
  int w;      //边的权重
  bool inSet;
  }Ele;
  Ele * eles[MAXN];//对于n个顶点,多有n*(n-1)条边
  int m;//m条边
  int pa[MAXN];
  int r[MAXN];
  void make_set(){
  int i;
  for(i=0;i<n;i++){
  pa[i] = i;
  r[i] = 1;
  }
  }
  int find_set(int x){
  if(x != pa[x]){
  pa[x] = find_set(x);
  }
  return pa[x];
  }
  bool unin_set(int x,int y){
  if(x==y) return;
  int xp = find_set(x);
  int yp = find_set(y);
  if(xp == yp) return false;//构成回路,不能合并
  if(r[xp]>r[yp]){
  pa[yp] = xp;//zhi小的放在zhi大的下面
  }
  else{
  pa[xp] = yp;
  if(r[xp] == r[yp]){
  r[yp]++;
  }
  }
  return true;
  }
  void sort(){
  int i,j;
  for(i=0;i<n-2;i++){
  int p = i;
  for(j = i+1;j<n-1;j++){
  if(eles[p].w > eles[j].w){
  p = j;
  }
  }
  if(p!=i){
  Ele * tmp = eles[i];eles[i] = eles[p];eles[p] = tmp;
  }
  }
  }
  /*
  int cmp(void * a,void * b){
  return (Ele*)a->w - (Ele*)b->w;
  }
  */
  int klske(){
  //将边由小到大排序
  //qsort(eles,sizeof(eles),sizeof(eles[0]),cmp)
  sort();
  int res;
  for(int i=0;i<m;i++){
  if(unin_set(find_set(eles[i].x),find_set(eles[i].y))){
  printf("%d %d %d ",else[i].x,eles[i].y,eles[i].w);
  }
  }
  return res;
  }
  int main(void){
  int i;
  scanf("%d",&m);
  //eles = (Ele*)malloc(n*sizeof(Ele));
  for(i=0;i<m-1;i++){
  int x,y,w;
  scanf("%d%d%d",&x,&y,&w);
  eles[i] = (Ele*)malloc(sizeof(Ele));
  eles[i]->x = x;
  eles[i]->y = y;
  eles[i]->w = w;
  eles[i]->inSet = false;
  }
  int res = klske(k);
  printf("%d ",res);
  for(i=0;i<m-1;i++){
  free(eles[i]);
  }
  return 0;
  }
  上面主要涉及的是一些数据结构,以及这些数据结构基本的算法,下面进入算法部分
  查找算法
  树表查找
  线索二叉树
  线索二叉树要求任何几节点的左子树比该节点的值小,右子树的值比该节点大。二叉排序树,主要涉及的是插入和搜索
  #include <stdio.h>
  #include <malloc.h>
  typedef struct bsTree{
  int m_iDate;
  struct bsTree * m_lChild/*左孩子指针*/,* m_rChild/*右孩子指针*/;
  } * BsTree,BsNode ;
  BsTree  insert(BsTree  bs,int x){
  BsNode * p = bs;
  BsNode * note  = p;
  BsNode * ct = NULL;
  while(p){
  if(x == p->m_iDate){
  return p;
  }
  else{
  // 记录上一个节点
  note = p;
  if(x < p->m_iDate) p = p->m_lChild;
  else p = p->m_rChild;
  }
  }
  ct = (BsNode * )malloc(sizeof(BsNode));
  ct->m_iDate = x;
  ct->m_rChild = NULL;
  ct->m_lChild = NULL;
  if(!bs){
  bs = ct;
  }else if(x < note->m_iDate){
  note->m_lChild = ct;
  }else note->m_rChild = ct;
  return bs;
  }
  BsNode * search(BsTree bs , int x){
  if(!bs || bs->m_iDate == x){
  return bs;
  }else if(x < bs->m_iDate){
  return search(bs->m_lChild,x);
  }else{
  return search(bs->m_rChild,x);
  }
  }
  有序表查找
  二分查找
  int binarySearch(int arr[],int l,int r,int key){
  while(l<r){
  int mid = (l+r) >> 1;
  if(arr[mid] > key){
  r = mid -1;
  }else if(arr[mid] < key){
  l = mid + 1;
  }else{
  return mid;
  }
  }
  return -l-1; // 返回可插入位置
  }
  排序算法
  冒泡排序
  以升序为例,冒泡排序每次扫描数组,将逆序修正,每次恰好将大的元素过五关斩六将推到后,再在剩余的元素重复操作
  void mpSort(int a[],int n){
  int i,j,swaped=1;
  for(i=0;i<n-1 && swaped;i++){
  swaped = 0;
  for(j=0;j<n-i;j++){
  if(a[j] > a[j+1]){
  swap(a[j],a[j+1]);
  swaped = 1;
  }
  }
  }
  }
  可见,冒泡排序的平均时间复杂度为O(n^2)
  选择排序
  以升序为例,每次扫描数组,找到小的元素直接挪到第一个来,再在剩余的数组中重复这样的操作
  void selectSort(int a[],int n){
  int i,j;
  for(i=0;i<n-1;i++){
  int p =i;
  for(j=i;j<n;j++){
  if(a[p] > a[j]){
  p = j;
  }
  }
  if(p != i){
  swap(p,i);
  }
  }
  }
  选择排序的平均时间复杂度也是O(n^2)
  直接插入排序
  直接插入排序不断在前面已经有序的序列中插入元素,并将元素向后挪。再重取一个元素重复这个操作
  #define MAXSIZE        20
  typedef int KeyType;
  typedef struct {
  KeyType key;
  }RedType;
  typedef struct{
  RedType r[MAXSIZE+1];
  int length;
  }SqList;
  //---------------------直接插入排序---------------------
  void InsertSort(SqList & L){
  int i,j;
  for (i=2;i<=L.length;i++)
  {
  L.r[0] = L.r[i];
  j = i -1;
  while (L.r[0].key < L.r[j].key)
  {
  L.r[j+1] = L.r[j];
  j--;
  }
  L.r[j+1] = L.r[0];
  }
  }
  插入排序的平均时间复杂度也是O(n^2)
  堆排序
  堆排序也是一种插入排序,不过是向二叉堆中插入元素,而且以堆排序中的方式存储二叉堆,则二叉堆必定是一棵完全二叉树,堆排序设计的主要操作是插入和删除之后的堆调整,使得堆保持为大底堆或者小底堆的状态。也是根节点始终保持为小或大,每次删除元素,将根节点元素与后元素交换,然后将堆的大小减1,然后进行堆调整,如此反复执行这样的删除操作。很显然,大底堆后会得到递增序列,小底堆会得到递减序列。
  /**
  *  堆调整
  *   在节点数为n的堆中从i节点开始进行堆调整
  */
  void heapAjust(int arr[] ,int i,int n){
  // 从i节点开始调整堆成为小底堆
  int tmp = arr[i],min;
  // 左孩子,对于节点i,它的左孩子是2i+1,右孩子是2i+2;
  for(;(min = 2*i + 1)<n;i = min){
  if(min+1 != n && arr[min] > arr[min+1]) min ++;
  if(arr[i] > arr[min]){
  //  将小元素放置到堆顶
  arr[i] = arr[min];
  }
  else break;
  }
  arr[i] = tmp;
  }
  void heapSort(int arr[],int n){
  // 建堆
  int i;
  for(i=n>>1;i>=0;i--){
  heapAjust(arr,i,n);
  }
  // 取堆顶,调整堆
  for(i = n-1;i>0;i--){
  //  每次取堆顶小的元素与后的元素交换,后会得到递减序列
  int tmp = arr[0];arr[0] = arr[i],arr[i] = tmp;
  //  删除一个元素后需要从根元素起重新调整堆
  heapAjust(arr,0,i);
  }
  }
  排序的平均时间复杂度为O(NLogN)
  快速排序
  说到快排,想起了大一上学期肖老师(教我C语言的老师)与我讨论的问题,当时懵懂无知,后才才知道那是快排。
  快排的思想也很简单,以升序为例,在序列中选一标杆,一般讲第一个元素作为标杆,然后将序列中比标杆小的元素放到标杆左边,将比标杆大的放到标杆右边。然后分别在左右两边重复这样的操作。
  void ksort(int a[],int l,int r){
  //  长度小于2有序
  if(r - l < 2) return;
  int start = l,end = r;
  while(l < r){
  // 向右去找到第一个比标杆大的数
  while(++l < end && a[l] <= a[start]);
  // 向左去找第一个比标杆小的数
  while(--r > start && a[r] >= a[start]);
  if(l < r) swap(a[l],a[r]); // 前面找到的两个数相对于标杆逆序 ,需交换过来 。l==r 不需要交换,
  }
  swap(a[start],a[r]);// 将标杆挪到正确的位置.
  // 对标杆左右两边重复算法,注意,这个l已经跑到r后面去了
  ksort(a,start,r);
  ksort(a,l,end);
  }
  快速排序的平均时间复杂度为O(NLogN)
  合并排序
  合并排序采用分治法的思想对数组进行分治,对半分开,分别对左右两边进行排序,然后将排序后的结果进行合并。按照这样的思想,递归做是方便的。
  int a[N],c[N];
  void mergeSort(l,r){
  int mid,i,j,tmp;
  if(r - 1 > l){
  mid = (l+r) >> 1;
  // 分别左右两天排序
  mergeSort(l,mid);
  mergeSort(mid,r);
  // 合并排序后的数组
  tmp  = l;
  for(i=l,j=mid;i<mid && j<r;){
  if(a[i] > a[j]) c[tmp++] = a[j++];
  else c[tmp++] = a[i++];
  }
  //  把剩余的接上
  if(j < r) {
  for(;j<r;j++) c[tmp++] = a[j];
  }else{
  for(;i<mid;i++) c[tmp++] = a[i];
  }
  // 将c数组覆盖到a里
  for(i=l;i<r;i++){
  a[i] = c[i];
  }
  }
  }