基本数据结构实现


考前抱佛脚┭┮﹏┭┮

记住这里大量用了c++的引用。。好方便,但是答题的时候记得换成 *和全局变量

一个算法应该具有以下特性:

  1. 有穷性
  2. 确定性 不存在二义性
  3. 可行性
  4. 有输入
  5. 有输出

1<n<log2n<n<nlog2n<n2<n3 (多项式)|| <2n<n!(指数)

1.线性表

顺序存储

SqList 是线性表

/*基本运算:
InitList(&L);
DestoryList(&L);
ListEmpty(L);
ListLength(L);
DispList(L);
GetElem(L,i,&e);
LocateElem(L,e);
ListInsert(&L,i,e);
ListDelete(&L,i,&e);
*/

/*线性表的顺序存储
i--; //转化为物理序号
if(i<1 || i>L->length+1) //插入的时候是允许插入第 n+1 位置的,所以还要额外+1
最后不要忘了改一下k的符号
*/

#include <stdio.h>
#include <malloc.h>
#define MaxSize 50
typedef int ElemType;
typedef struct
{
    ElemType data[MaxSize];
    int length;
} SqList; //顺序表类型

//需要改变的就传入引用,不需要改变的就直接传入指针就好了。
void CreateList(SqList *&L, ElemType a[], int n)
{
    int i = 0, k = 0; //用来统计数量,i用来当索引,但是为什么不直接用i呢?
    L = (SqList *)malloc(sizeof(SqList));
    while (i < n)
    {
        L->data[i] = a[i];
        k++;
        i++;
    }
    L->length = k;
}

void InitList(SqList *&L)
{
    L = (SqList *)malloc(sizeof(SqList));
    L->length = 0;
}

void DestroyList(SqList *&L)
{
    free(L);
}

bool ListEmpty(SqList *L)
{
    return L->length == 0;
}

int ListLength(SqList *L)
{
    return L->length;
}

void DispList(SqList *L)
{
    for (int i = 0; i < L->length; i++)
    {
        printf("%d", L->data[i]);
        printf("\n");
    }
}

bool GetElem(SqList *L, int i, ElemType &e)
{
    if (i < 1 || i > L->length)
        return false;
    e = L->data[i - 1];
    return true;
}

int LocateElem(SqList *L, ElemType e)
{
    int i = 0;
    while (i < L->length && L->data[i] != e)
        i++;
    if (i >= L->length)
        return 0;
    else
        return i + 1; //记住逻辑序号和物理序号差了一个1嗷
}

bool ListInsert(SqList *&L, int i, ElemType e)
{
    int j = L->length;
    if (i < 1 || i > L->length + 1) //插入的时候是允许插入最后一个位置的,所以还要额外+1
        return false;
    i--; //转化为物理序号
    for (; j > i; j--)
        L->data[j] = L->data[j - 1];
    L->data[i] = e;
    L->length++;
}

bool ListDelete(SqList *&L, int i, ElemType e)
{
    if (i < 1 || i > L->length)
        return false;
    i--; //转换成物理非常重要!
    int j = L->length;
    while (j > i)
    {
        L->data[j - 1] = L->data[j];
    }
    L->length--;
}
//****************************************************************************************************
//删除L中所有值等于 x 的元素,双指针扫描
void delnode1(SqList *&L, ElemType x)
{
    int k = 0, i = 0;
    for (; i < L->length; i++)
    {
        if (L->data[i] != x)
        {
            L->data[k] == L->data[i];
            k++;
        }
    }
    L->length = k;
}
//用k记录个数
void delnode2(SqList *&L, ElemType x)
{
    int k = 0, i = 0;
    for (i = 0; i < L->length; i++)
    {
        if (L->data[i] == x)
            k++;
        else
            L->data[i - k] = L->data[k];
    }
    L->length -= k;
}

//这一片巧妙利用“缺位,补位”的技巧,节省了许多空间和时间,直接对换会消耗一个额外内存,不如缺位补位
//感觉不够通用,不过现在懒得改了,很多二分的其实都是pattition,跟python提供的key函数参数一样
void partition1(SqList *&L)
{
    int i = 0, j = L->length - 1;
    ElemType pivot = L->data[0]; //中文翻译:枢
    while (i < j)
    {
        while (j > i && L->data[j] > pivot)
            j--;
        L->data[i] = L->data[j];
        while (i < j && L->data[i] < pivot)
            i++;
        L->data[j] = L->data[i];
    }
    L->data[i] = pivot;
}

//顺序表归并算法,要求不改变原有的LA和LB
void UnionList(SqList *LA, SqList *LB, SqList *&LC)
{
    int i =0,j = 0,k = 0;//i,j为两组下标
    while(i<LA->length && j<LB->length){
        if (LA->data[i]<LB->data[j])
        {
            LC->data[k] = LA->data[i];
            i++,k++;
        }
        else{
            LC->data[k] = LB->data[j];
            j++,k++;
        }
    }
    while (i<LA->length)
    {
        LC->data[k] = LA->data[i];
        i++,k++;
    }
    
    while (j<LB->length)
    {
        LC->data[k] = LB->data[j];
        j++,k++;
    }
    LC->length = k;
}

单链表

p是遍历用的节点,有一个工具节点用来接受创建或者进行删除,insert用s,delete用q

尾插法的工具节点r

/*单链表
记得要设置最后一个是NULL
LinkNode *pre = L,*p = L->next   //这个初始化挺好的!!!
分析多或少1的时候,从开头0各和少数几个的极端情况分析。
头节点L是没有存放数据的,也不算进长度,这就解释了为什么p有时是L有时是L->next,
插入和删除都是先j<i-1找到第i-1然后操作,所以初始化为p = L,因为是对后面那个操作
*/
#include <stdio.h>
#include <malloc.h>
#define MaxSize 50
typedef int ElemType;
typedef struct LNode
{
    ElemType data;
    struct LNode *next;
} LinkNode; //顺序表类型

//头插法
/*这里如果只传指针的话,只是传了一个指针的副本,相当于告诉你我的地址在这里,你可以修改我地址里的东西,但是这没有什么意义,因为我需要操作的是这个指针,不然操作全都传不回去,相当于你把一个副本连在一个结构里了,虽然这个副本和原来的指针有着相同的地址,但是此时我并不需要这个地址,我此时需要的是指针本身。加引用才是把原本的指针传过来了。*/
void CreateListF(LinkNode *&L, ElemType a[], int n)
{
    LinkNode *s; //工具节点上线
    L = (LinkNode *)malloc(sizeof(LinkNode));
    L->next = NULL; //经典创建
    for (int i = 0; i < n; i++)
    {
        s = (LinkNode *)malloc(sizeof(LinkNode));
        s->data = a[i];
        s->next = L->next;
        L->next = s;
        //头插四件套
    }
}

//尾插法
void CreateListR(LinkNode *&L, ElemType a[], int n)
{
    LinkNode *s, *r;
    L = (LinkNode *)malloc(sizeof(LinkNode));
    r = L;
    for (int i = 0; i < n; i++)
    {
        s = (LinkNode *)malloc(sizeof(LinkNode));
        s->data = a[i];
        r->next = s;
        r = s;
    }
    r->next = NULL;
}

void InitList(LinkNode *&L)
{
    L = (LinkNode *)malloc(sizeof(LinkNode));
    L->next = NULL;
}

void DestroyList(LinkNode *&L)
{
    LinkNode *pre = L, *p = L->next; //这个初始化挺好的!!!
    while (p != NULL)
    {
        free(pre);
        pre = p;
        p = p->next;
    }
    free(pre); //最后一步别忘了!!
}

bool ListEmpty(LinkNode *L)
{
    return (L->next == NULL);
}

int ListLength(LinkNode *L)
{
    int n = 0;
    LinkNode *p = L;
    while (p->next != NULL)
    {
        n++;
        p = p->next;
    }
    return n;
}

void DispList(LinkNode *L)
{
    LinkNode *p = L->next;
    while (p != NULL)
    {
        printf("%d", p->data);
        p->next;
    }
    printf("\n");
}

bool GetElem(LinkNode *L, int i, ElemType &e)
{
    int j = 0;
    if (i <= 0)
        return false; //别漏了
    LinkNode *p = L;
    while (j < i && p)
    {
        j++;
        p = p->next;
    }
    if (p == NULL)
        return false;
    else
    {
        e = p->data;
        return true;
    }
}

//
int LocateElem(LinkNode *L, ElemType e)
{
    int i = 1;//逻辑修正
    LinkNode *p = L->next;
    while (p != NULL && p->data != e)
    {
        p = p->next;
        i++;
    }
    if (p == NULL)
        return 0;
    else
        return i;
}

bool ListInsert(LinkNode *&L, int i, ElemType e)
{
    int j = 0;
    LinkNode *p = L, *s;
    if (i <= 0)
        return false;
    while (j < i - 1 && p != NULL)
    {
        j++;
        p = p->next;
    }
    if (p == NULL)
        return false;
    else
    {
        s = (LinkNode *)malloc(sizeof(LinkNode));
        s->data = e;
        s->next = p->next;
        p->next = s;
        return true;
    }
}

bool ListDelete(LinkNode *&L, int i, ElemType &e)
{
    int j = 0;
    LinkNode *p = L, *q;
    if (i <= 0)
        return false;
    while (j < i - 1 && p != NULL)
    {
        j++;
        p = p->next;
    }
    if (p == NULL)
        return false;
    else
    {
        q = p->next;
        if (q == NULL)
            return false; //多检验一次,因为检验不到这里
/*不如直接if(!(p && p->next)) return false;*/
        e = q->data;
        p->next = q->next;
        free(q);
        return true;
    }
}

//****************************************************************************************************
//删除一个单链表L中元素最大的节点(假设唯一)插入一个标记就好了
void delmaxnode(LinkNode *&L)
{
    LinkNode *p = L->next, *pre = L, *maxp = p, *maxpre = pre;
    while (p != NULL)
    {
        if (maxp->data < p->data)
        {
            maxp = p;
            maxpre = pre;
        }
        pre = p;
        p = p->next;
    }
    maxpre->next = maxp->next;
    free(maxp);
}

//使元素递增有序排列,没有想象中巧妙
void sort(LinkNode *&L)
{
    LinkNode *p, *pre, *q;
    p = L->next->next;
    L->next->next = NULL;
    //p接下了第二个节点,原来的头和首被当作了新的有序链表
    while (p != NULL)
    {
        q = p->next; //保存原来剩下的链
        pre = L;     //每次pre都从头扫描一遍。。。
        while (pre->next != NULL && pre->next->data < p->data)
            pre = pre->next;//找到合适的位置
        p->next = pre->next;
        pre->next = p;//p接在这
        p = q;//去q保存的地方继续操作
    }
}

void UnionList1(LinkNode *LA,LinkNode *LB,LinkNode *&LC){
    LinkNode *pa = LA->next,*pb = LB->next,*r,*s;//尾插法和创建
    LC =(LinkNode *)malloc(sizeof(LinkNode));
    while(pa!=NULL && pb!=NULL){
        if(pa->data < pb->data){
            s =(LinkNode *)malloc(sizeof(LinkNode));
            s->data = pa->data;
            r->next = s;
            r = s;
            pa = pa->next;
        }
        else{
            if(pb->data > pa->data){
            s =(LinkNode *)malloc(sizeof(LinkNode));
            s->data = pb->data;
            r->next = s;
            r = s;
            pb = pb->next;
        }
    }
    while (pa!=NULL)
    {
        s = (LinkNode *)malloc(sizeof(LinkNode));
        s->data = pa->data;
        r->next = s;
        r = s;
        pa = pa->next;
    }
    while (pb!=NULL)
    {
        s = (LinkNode *)malloc(sizeof(LinkNode));
        s->data = pb->data;
        r->next = s;
        r = s;
        pb = pb->next;
    }
    r->next =NULL;
}

循环双链表

/*双链表
return true;//不要漏了
*/
#include <stdio.h>
#include <malloc.h>
typedef int ElemType;
typedef struct DNode
{
    ElemType data;
    struct DNode *prior;
    struct DNode *next;
} DLinkNode; //顺序表类型

//建立头插法
void CreateListF(DLinkNode *&L, ElemType a[], int n)
{
    DLinkNode *s;
    L = (DLinkNode *)malloc(sizeof(DLinkNode));
    L->prior = L->next = NULL;
    for (int i = 0; i < n; i++)
    {
        s = (DLinkNode *)malloc(sizeof(DLinkNode));
        s->prior = L;
        s->data = a[i];
        s->next = L->next;
        //一共要链4条,但是这一条可能不需要链接👇
        if (L->next != NULL)
            L->next->prior = s;
        L->next = s;
    }
}

void CreateListR(DLinkNode *&L, ElemType a[], int n)
{
    DLinkNode *s, *r;
    L = (DLinkNode *)malloc(sizeof(DLinkNode));
    r = L;
    for (int i = 0; i < n; i++)
    {
        s = (DLinkNode *)malloc(sizeof(DLinkNode));
        s->data = a[i];
        r->next = s;
        s->prior = r;
        r = s;
    }
    r->next = NULL;
}

//其余操作很多类似单链表,插入和删除有点区别罢了,工具节点叫s
bool ListInsert(DLinkNode *&L, int i, ElemType e)
{
    int j = 0;
    DLinkNode *p = L, *s;
    if (i <= 0)
        return false;
    while (j < i - 1 && p != NULL)
    {
        j++;
        p = p->next;
    }
    if (p == NULL)
        return false;
    else
    {
        s = (DLinkNode *)malloc(sizeof(DLinkNode));
        s->data = e;
        s->prior = p;
        s->next = p->next;
        if (p->next != NULL)
            p->next->prior = s;
        p->next = s;
        return true; //不要漏了
    }
}
//****************************************************************************************************
//工具节点叫q
bool ListDelete(DLinkNode *&L, int i, ElemType &e)
{
    int j = 0;
    DLinkNode *p = L, *q;
    if (i <= 0)
        return false;
    while (j < i && p != NULL)
    {
        j++;
        p = p->next;
    }
    if (p == NULL)
        return false;
    else
    {
        q = p->next;
        e = q->data;
        p->next = q->next;
        if (q->next != NULL)
            q->next->prior = p;
        free(q);
        return true;
    }
}

//逆置,头插法就能进行逆置,头节点还能保留
void reverse(DLinkNode *&L)
{
    DLinkNode *p = L->next, *q;
    L->next = NULL;
    while (p != NULL)
    {
        q = p->next;
        p->next = L->next;
        if (L->next != NULL)
            L->next->prior = p;
        p->prior = L;
        L->next = p->next;
        p = q; //继续指向后继节点
    }
}

//双链表的删除是不需要工具节点的,这里用一个循环双链表的例子展示一下,删除第一个data为x的节点
bool delelem(DLinkNode *&L, ElemType x)
{
    DLinkNode *p = L->next; //不需要工具点,p可以直接指向操作节点,而不是前一个
    while (p != L && p->data != x)
        p = p->next;
    if (p == L)
        return false;
    else
    {
        p->next->prior = p->prior;
        p->prior->next = p->next;
        free(p);
        return true;
    }
}

//循环双链表判断对称
bool Symm(DLinkNode *L)
{
    bool same = true; //这类题的典型flag
    DLinkNode *p = L->next;
    DLinkNode *q = L->prior;
    while (same)
    {
        if (p->data != q->data)
            same = false;
        else
        {
            if (p == q || p == q->prior)
                break; //当相等(奇数)或相邻(偶数),为结束条件
            q = q->prior;
            p = p->next;
        }
    }
    return same;
}

2.栈和队列

顺序栈

#include <stdio.h>
#include <malloc.h>
#define MAXSIZE 50
typedef int ElemType;
typedef struct
{
    ElemType data[MAXSIZE];
    int top;
} SqStack;

void InitStack(SqStack *&s)
{
    s = (SqStack *)malloc(sizeof(SqStack));
    s->top = -1;
}

void DestroyStack(SqStack *&s)
{
    free(s);
}

bool StackEmpty(SqStack *s)
{
    return s->top == -1;
}
bool Push(SqStack *&s, ElemType e)
{
    if (s->top == MAXSIZE - 1)
        return false;
    s->top++;
    s->data[s->top] = e;
    return true;
}

bool Pop(SqStack *&s, ElemType &e)
{
    if (s->top == -1)
        return false;
    e = s->data[s->top];
    s->top--;
    return true;
}

bool GetTop(SqStack *s, ElemType &e)
{
    if (s->top == -1)
        return false;
    e = s->data[s->top];
    return true;
}

//判断字符串是否为对称串
bool Symmetry(ElemType str[])
{
    int i;
    ElemType e;
    SqStack *st;
    InitStack(st);
    for (i = 0; str[i] != '\0'; i++)
        Push(st, str[i]);
    for (int i = 0; str[i] != '\0'; i++)
    {
        Pop(st, e);
        if (e != str[i])
        {
            DestroyStack(st);
            return false;
        }
        DestroyStack(st);
        return true;
    }
}

/*
共享栈,适用于一方可能用到满,另一方还有很多空间
栈空:top1 == -1   top2 == MAXSIZE
栈满:top1 ==top2-1
进栈出栈的时候top2的操作和top1是反的
循环队列A[0..n-1]存放其元素值,F表示队头元素所在的前一个位置,R表示队尾元素的位置。则当前队列中的元素数是
当R>F时,元素个数为  R-F个
当R<F时,元素个数为 n - (F-R)= R-F+n个
*/

链栈

//选用带头结点的,更加便捷
#include <stdio.h>
#include <malloc.h>
#define MAXSIZE 50
typedef int ElemType;
typedef struct linknode
{
    ElemType data;
    linknode *next;
} LinkStNode; //这名字真长

void InitStack(LinkStNode *&s)
{
    s = (LinkStNode *)malloc(sizeof(LinkStNode));
    s->next = NULL;
}

void DestroyStack(LinkStNode *&s)
{
    LinkStNode *pre = s, *p = s->next;
    while (p != NULL)
    {
        free(pre);
        pre = p;
        p = p->next;
    }
    free(pre);
}

bool StackEmpty(LinkStNode *s)
{
    return (s->next == NULL);
}

void Push(LinkStNode *&s, ElemType e)
{
    LinkStNode *p;
    p = (LinkStNode *)malloc(sizeof(LinkStNode));
    p->data = e;
    p->next = s->next;
    s->next = p;
}

bool Pop(LinkStNode *&s, ElemType &e)
{
    LinkStNode *p;
    if (s->next == NULL)
        return false;
    p = s->next;
    e = p->data;
    s->next = p->next;
    free(p);
    return true;
}

bool GetTop(LinkStNode *s, ElemType &e)
{
    if (s->next == NULL)
        return false;
    e = s->next->data;
    return true;
}

//迷宫问题和逆波兰表达式在P87

顺序队列

//在队头进行删除,在队尾进行插入
//记住,front指的是空的地方
#include <stdio.h>
#include <malloc.h>
#define MAXSIZE 50
typedef int Elemtype;
typedef struct
{
    Elemtype data[MAXSIZE];
    int front, rear;
} SqQueue;

void InitQueue(SqQueue *&q)
{
    q = (SqQueue *)malloc(sizeof(SqQueue));
    q->front = q->rear = -1;
}

void DestoryQueue(SqQueue *&q)
{
    free(q);
}

bool QueueEmpty(SqQueue *q)
{
    return (q->front == q->rear);
}

bool enQueue(SqQueue *&q, Elemtype e)
{
    if (q->rear == MAXSIZE - 1)
        return false;
    q->rear++;
    q->data[q->rear] = e;
    return true;
}

bool deQueue(SqQueue *&q, Elemtype &e)
{
    if (q->front == q->rear)
        return false;
    q->front++;
    e = q->data[q->front];
    return true;
}

/****************************************************************************************/
//环形队列
void InitQueue(SqQueue *&q)
{
    q = (SqQueue *)malloc(sizeof(SqQueue));
    q->front = q->rear = 0; //不是-1了
}

bool enQueue(SqQueue *&q, Elemtype e)
{
    if ((q->rear + 1) % MAXSIZE == q->front)
        return false;
    q->rear = (q->rear + 1) % MAXSIZE;
    q->data[q->rear] = e;
    return true;
}

bool dequeue(SqQueue *&q, Elemtype e)
{
    if (q->front == q->rear)
        return false;
    q->front = (q->front + 1) % MAXSIZE;
    return true;
}

//这两个if判断条件挺有趣的
//队列非常有层次感,有很多妙用,既可以用于递归层次,又可以用于重复
//比如书上这个很强的1212报到1出列的问题
void number(int n)
{
    int i;
    ElemType e;
    SqQueue *q;
    for (int i = 0; i <= n; i++)
        enQueue(q, i);
    printf("报数出列顺序:\n");
    while (!QueueEmpty(q))
    {
        dequeue(q, e);
        printf("%d", e);
        if (!QueueEmpty(q))
        {
            dequeue(q, e);
            enQueue(q, e); //偶数惨遭重新排队哈哈
        }
        printf("\n");
        DestoryQueue(q);
    }
}
//还可以定制双端队列什么的,其实原理都差不多

链队

#include <stdio.h>
#include <malloc.h>

typedef int ElemType;
typedef struct qnode
{
    ElemType data;
    struct qnode *next;
} DataNode;

//需要一个链队节点
typedef struct
{
    DataNode *front;
    DataNode *rear;
} LinkQuNode;

void InitQueue(LinkQuNode *&q)
{
    q = (LinkQuNode *)malloc(sizeof(LinkQuNode));
    q->front = q->rear = NULL;
}

void DestroyQueue(LinkQuNode *&q)
{
    DataNode *pre = q->front, *p;
    if (pre != NULL)
    {
        p = pre->next;
        while (p != NULL)
        {
            free(pre);
            pre = p;
            p = pre->next;
        }
        free(pre); //这个怎么总是忘掉啊!
    }
    free(q);
}

bool QueueEmpty(LinkQuNode *q)
{
    return (q->rear == NULL);
}

void enqueue(LinkQuNode *&q, ElemType e) //因为无限长度,所以类型是bool
{
    DataNode *p;
    p = (DataNode *)malloc(sizeof(DataNode));
    p->data = e;
    p->next = NULL;
    if (q->rear == NULL)
        q->front = q->rear = p; //还要判断是不是空列表!!!
    else
    {
        q->rear->next = p;
        q->rear = p;
    }
}

bool dequeue(LinkQuNode *&q, ElemType &e)
{
    DataNode *t;
    if (q->rear == NULL)
        return false;
    t = q->front;//两种情况都可以先将t指过来
    if (q->front == q->rear) //只有一个节点
        q->front = q->rear = NULL;
    else
        q->front = t->next;
    free(t);
    return true;
}

3.串

顺序串

若串S=′software′,其子串的数目是()
字符串的子串,就是字符串中的某一个连续片段。截取一个字符串长度需要一个起始位置和结束位置。字符串“software”有8个字符,可是设置间隔的位置有9个,使用C(9,2)=36即可求得字符串“software”的所有子串。因为题目标明空串也是子串,故还需要加上1,总共37个子串。所以答案选37
空格串是指由空格字符所组成的字符串,其长度等于空格个数。 。
组成串的数据元素只能是字符。
/*
StrAssign(&s,cstr)将cstr字符串内容赋给s
DestoryStr(&s)销毁串
StrCopy(&s,t)串复制,将串t赋给串s
StrEqual(s,t)
StrLength(s)
Concat(s,t)返回一个新串,不改变原来的串
SubStr(s,i,j)求子串
InsStr(s1,i,s2)字串插入
DelStr(s,i,j)字串删除
RepStr(s,i,j,t)字串替换
DispStr(s)串输出
*/
#include <stdio.h>
#include <malloc.h>
#define MAXSIZE 50
typedef struct
{
    char data[MAXSIZE];
    int length;
} SqString;

//把字符串转换成串的数据结构
void StrAssign(SqString &s, char cstr[])
{
    int i;
    for (int i = 0; cstr[i] != '\0'; i++)
        s.data[i] = cstr[i];
    s.length = i;
}

void DestoryStr(SqString &s) {} //因为这个不是malloc分配的,会自动回收
void StrCopy(SqString &s, SqString t)
{
    int i;
    for (i = 0; i < t.length; i++)
    {
        s.data[i] = t.data[i];
        s.length = t.length;
    }
}

//判断相等
bool StrEqual(SqString s, SqString t)
{
    bool same = true;
    int i;
    if (s.length != t.length) //能用长度排除就直接去世吧
        same = false;
    else
        for (i = 0; i < s.length; i++)
            if (s.data[i] != t.data[i])
            {
                same = false;
                break;
            }
    return same;
}

int StrLength(SqString s)
{
    return s.length;
}

//链接
SqString Concat(SqString s, SqString t)
{
    SqString str;
    int i;
    str.length = s.length + t.length;
    for (i = 0; i < s.length; i++)
        str.data[i] = s.data[i];
    for (i = 0; i < t.length; i++)
        str.data[s.length + i] = t.data[i];//从这时起,下标注意进行简单的计算
    return str;
}

//求子串,返回从第i个字符开始的,连续j个字符组成的字串
SqString SubStr(SqString s, int i, int j)
{
    int k;
    SqString str;
    str.length = 0;
    if (i <= 0 || i > s.length || j < 0 || i + j - 1 > s.length) 
        //i+j-1注意一下下,理解不了就最简单的i=1,j=2,s
        return str;
    for (k = i - 1; k < i + j - 1; k++) //i-1开始,因为逻辑和物理差1
    {
        str.data[k - i + 1] = s.data[k];
    }
    str.length = j;
    return str;
}

//这个里面的加减1有点骚,理解好了这类题就不怕了。
SqString IntStr(SqString s1, int i, SqString s2)
{
    int j;
    SqString str;
    str.length = 0;
    if (i <= 0 || i > s1.length + 1) //插入的时候是可以多插一位的
        return str;                  //返回空串
    for (j = 0; j < i - 1; j++)
        str.data[j] = s1.data[j];
    for (j = 0; j < s2.length; j++)
        str.data[i + j - 1] = s2.data[j];
    for (j = i - 1; j < s2.length; j++)
        str.data[s2.length + j] = s1.data[j];
    str.length = s1.length + s2.length;
    return str;
}

SqString DelStr(SqString s, int i, int j)
{
    int k;
    SqString str;
    str.length = 0; //便于返回空串
    if (i <= 0 || i > s.length || i + j - 1 > s.length || j < 0)
        return str;
    for (k = 0; k < i; k++)
        str.data[k] = s.data[k];
    for (k = i + j - 1; k < s.length; k++)
        str.data[k - j] = s.data[k];
    str.length = s.length - j;
}

//将i开始到j的字串用t替换
//我好像有点明白了,他在for循环里用的索引对标某个长度,很简单,其他的操作通过改变数组索引的表达式来实现。
SqString RepStr(SqString s, int i, int j, SqString t)
{
    int k = 0;
    SqString str;
    str.length = 0;
    if (i <= 0 || i > s.length || i + j - 1 > s.length - 1 || j < 0)
        return str;
    for (k = 0; k < i; k++)
        str.data[k] = s.data[k];
    for (k = 0; k < t.length; k++)
        str.data[k + j] = t.data[k];
    for (k = i + j - 1; k < s.length; k++)
        str.data[t.length - j + k] = s.data[k];
    str.length = s.length - j + t.length;
    return str;
}

void DispStr(SqString s)
{
    int i;
    if (s.length > 0)
    {
        for (i = 0; i < s.length; i++)
            printf("%c", s.data[i]);
    }
    printf("\n");
}

//比较大小
int Strcmp(SqString s, SqString t)
{
    int i, comlen;
    if (s.length < t.length)
        comlen = s.length;
    else
        comlen = t.length; //这里也包括了相等的情况
    for (i = 0; i < comlen; i++)
        if (s.data[i] > t.data[i])
            return 1;
        else if (s.data[i] < t.data[i])
            return -1;
    if (s.length == t.length)
        return 0;
    else if (s.length > t.length)
        return 1;
    else
        return -1;
}

//求s中出现的第一个最长连续相同字符构成的平台,用index开始索引,maxlen保存长度
void LongestString(SqString s, int &index, int &maxlen)
{
    index = maxlen = 0;
    int length, i = 1, start; //用来保存局部的  这种全局和局部都有对应的变量!
    while (i < s.length)
    {
        start = i - 1; //留个物理索引
        length = 1;
        while (i < s.length && s.data[i] == s.data[i - 1])
        {
            i++;
            length++;
        }
        if (maxlen < length)
        {
            maxlen = length;
            index = start;
        }
    }
}


//Brute-Force
int BF(SqString s, SqString t)
{
    int i =0,j=0;
    while(i<s.length && j<t.length)
    {
        if(s.data[i] == t.data[j])
        i++,j++;
        else
        i = i-j+1,j=0;//i回退到最初的后一位,j清空
    }
    if(j>=t.length) return(i-t.length);
    else return -1;
}

链串(堆串)

#include <stdio.h>
#include <malloc.h>
//链串的创建都是用尾插法,因此都有一个工具节点r
//带头节点的单链表作为链串,结点大小是每个结点存储的字符个数,未占用的用特殊符号(#)填补
//链串中,节点大小越大,存储密度越大,基本操作有所不便,适合很少修改的情况,这里规定大小为1
typedef struct snode
{
    char data; //存放字符
    struct snode *next;
} LinkStrNode;

//要用尾插法保证顺序
void StrAssign(LinkStrNode *&s, char cstr[])
{
    int i;
    LinkStrNode *r, *p;
    s = (LinkStrNode *)malloc(sizeof(LinkStrNode));
    r = s;
    for (i = 0; cstr[i] != '\0'; i++)
    {
        p = (LinkStrNode *)malloc(sizeof(LinkStrNode));
        p->data = cstr[i];
        r->next = p;
        r = p;
    }
}

void DestroyStr(LinkStrNode *&s)
{
    LinkStrNode *pre = s, *p = s->next;
    while (p != NULL)
    {
        free(p);
        pre = p;
        p = pre->next;
    }
    free(pre);
}

void StrCopy(LinkStrNode *&s, LinkStrNode *t)
{
    LinkStrNode *p = t->next, *q, *r;
    s = (LinkStrNode *)malloc(sizeof(LinkStrNode));
    r = s;
    while (p != NULL)
    {
        q = (LinkStrNode *)malloc(sizeof(LinkStrNode));
        q->data = p->data;
        r->next = q;
        r = q;
        p = p->next;
    }
    r->next = NULL;
}

bool StrEqual(LinkStrNode *s, LinkStrNode *t)
{
    LinkStrNode *p = s->next, *q = t->next; //直接比较本身
    while (p != NULL && q != NULL && p->data == q->data)
    {
        p = p->next;
        q = q->next;
    }
    if (p->data == NULL && q->data == NULL)
        return true;
    else
        return false;
}

int StrLength(LinkStrNode *s)
{
    int i = 0;
    LinkStrNode *p = s->next;
    while (p->data != NULL)
    {
        p = p->next;
        i++;
    }
    return i;
}

//链接两个数组其实就是分别遍历一遍
LinkStrNode *Concat(LinkStrNode *s, LinkStrNode *t)
{
    LinkStrNode *str, *p = s->next, *q = t->next, *r;
    str = (LinkStrNode *)malloc(sizeof(LinkStrNode));
    while (p != NULL)
    {
        q = (LinkStrNode *)malloc(sizeof(LinkStrNode));
        q->data = p->data;
        r->next = q;
        r = q;
        p = p->next;
    }
    p = t->next; //这样直接转过来,少一个工具节点,是我蠢了
    while (p != NULL)
    {
        q = (LinkStrNode *)malloc(sizeof(LinkStrNode));
        q->data = p->data;
        r->next = q;
        r = q;
        p = p->next;
    }
    r->next = NULL;
    return str;
}

//当参数不正确的时候返回一个空串,这里需要调用之前已经封装好的函数
LinkStrNode *Substr(LinkStrNode *s, int i, int j)
{
    int k;
    LinkStrNode *str, *p = s->next, *q, *r; //经典四件套哈哈
    str = (LinkStrNode *)malloc(sizeof(LinkStrNode));
    str->next = NULL; //先把空串准备好在这里
    r = str;
    if (i <= 0 || i > StrLength(s) || j < 0 || i + j - 1 > StrLength(s))
        return str;
    for (k = 1; k < i; k++) //这里的k取得是1,因为要取到逻辑顺序第i位的前一位,而不是物理顺序
        p = p->next;
    for (k = 1; k <= j; k++)
    {
        q = (LinkStrNode *)malloc(sizeof(LinkStrNode));
        q->data = p->data;
        r->next = q;
        r = q;
        p = p->next;
    }
    r->next = p;
    return str;
}

//怎么感觉这里所有的其实都差不多,都是链表啊啥的,没啥意思

LinkStrNode *InsStr(LinkStrNode *s, int i, LinkStrNode *t)
{
    int k;
    LinkStrNode *str, *p = s->next, *q, *r, *p1 = t->next;
    str = (LinkStrNode *)malloc(sizeof(LinkStrNode));
    str->next = NULL;
    r = str;
    if (i <= 0 || i > StrLength(s) + 1)
        return str;
    for (k = 1; k < i; k++)
    {
        q = (LinkStrNode *)malloc(sizeof(LinkStrNode));
        q->data = p->data;
        r->next = q;
        r = q;
        p = p->next;
    }
    while (p1 != NULL)
    {
        q = (LinkStrNode *)malloc(sizeof(LinkStrNode));
        q->data = p1->data;
        r->next = q;
        r = q;
        p1 = p1->next;
    }
    while (p != NULL)
    {
        q = (LinkStrNode *)malloc(sizeof(LinkStrNode));
        q->data = p->data;
        r->next = q;
        r = q;
        p = p->next;
    }
    r->next = NULL;
    return str;
}

//这个基本上都是完全一样的,直接copy一部分,瞬间刷完,我吐了,这些那么相似的还搞
LinkStrNode *DelStr(LinkStrNode *s, int i, int j)
{
    int k;
    LinkStrNode *str, *p = s->next, *q, *r;
    str = (LinkStrNode *)malloc(sizeof(LinkStrNode));
    str->next = NULL;
    if (i <= 0 || i > StrLength(s) || j < 0 || i + j - 1 > StrLength(s))
        return str;
    for (k = 1; k < i; k++)
    {
        q = (LinkStrNode *)malloc(sizeof(LinkStrNode));
        q->data = p->data;
        r->next = q;
        r = q;
        p = p->next;
    }
    for (k = 0; k < j; k++) //这里的k应该是0,因为这个就不是逻辑序号,而是物理上的序号了
        p = p->next;
    while (p != NULL)
    {
        q = (LinkStrNode *)malloc(sizeof(LinkStrNode));
        q->data = p->data;
        r->next = q;
        r = q;
        p = p->next;
    }
    r->next = NULL;
    return str;
}

LinkStrNode *RepStr(LinkStrNode *s, int i, int j, LinkStrNode *t)
{
    int k;
    LinkStrNode *str, *p = s->next, *q, *r, *p1 = t->next;
    str = (LinkStrNode *)malloc(sizeof(LinkStrNode));
    str->next = NULL;
    if (i <= 0 || i > StrLength(s) || j < 0 || i + j - 1 > StrLength(s))
        return str;
    for (k = 0; k < i - 1; k++) //这样的其实也是逻辑,所以i-1,使用哪种看自己喜欢吧
    {
        q = (LinkStrNode *)malloc(sizeof(LinkStrNode));
        q->data = p->data;
        r->next = q;
        r = q;
        p = p->next;
    }
    while (p1 != NULL)
    {
        q = (LinkStrNode *)malloc(sizeof(LinkStrNode));
        q->data = p->data;
        r->next = q;
        r = q;
        p = p->next;
    }
    for (k = 0; k < j; k++)
        p = p->next;
    while (p != NULL)
    {
        q = (LinkStrNode *)malloc(sizeof(LinkStrNode));
        q->data = p->data;
        r->next = q;
        r = q;
        p = p->next;
    }
    r->next = NULL;
    return str;
}
//好垃圾啊,,真的全都一样的,我感觉我都能背下来了

void DispStr(LinkStrNode s)
{
    LinkStrNode *p = s->next;
    while (p != NULL)
    {
        printf("%c", p->data);
        p = p->next;
    }
    printf("\n");
}

//

4.递归

只要确保了子结构和大结构 结构一致,就不用考虑过多细节,在设计算法的时候留意递归调用的意义,比如求树的深度,在某个地方需要子树的深度了,就直接调用就好,只用关心你这最宏观的一层,不用考虑内在的东西,这就是递归的魔力。因此【递归模型】非常重要

//在算法设计中,任何间接递归都可以转换为直接递归,这是自顶向下,同时考虑最底部的
//如果递归过程或者递归函数的递归调用语句是最后一条执行语句,则称为尾递归
/* 三个条件:
1.问题可以转化为1个或多个
2.递归调用的次数必须是有限的
3.必须有用来结束递归的条件
*/
用到递归的三种情况:
1. 数学公式,数列本身的定义就是递归的,比如n!和斐波那契数列
2. 数据结构是递归的,比如指向本身的链表,对于这样的结构,递归方法既方便又有效
但是要注意大结构和小结构保持一致性,比如对单链表设计递归算法时,通常采用不带头结点的单链表。
int Sum(LinkNode *L){
	if(L == NULL) return 0;
	else return(L->data + Sum(L->next))
}
3. 问题的求解是递归的,比如汉诺塔P149
void Hanoil (int n,char X,char Y,char Z)
{
	if(n==1) printf("将第%d个盘片从%c移动到%c",n,X,Z)
	else{
		Hanoil(n-1,X,Z,Y)
		printf("将第%d个盘片从%c移动到%c",n,X,Z)
	}
}

递归模型:递归出口和递归体,可以看P150

可以说递归的思想来自数学归纳法

#include <stdio.h>
#include <malloc.h>
#define MAXSIZE 50
typedef struct
{
    int n;
    char x, y, z;
    bool flag;
} ElemType;
typedef struct
{
    ElemType data[MAXSIZE];
    int top;
} StackType;

void InitStack(StackType *&s)
{
    s = (StackType *)malloc(sizeof(StackType));
    s->top = -1;
}

void DestroyStack(StackType *&s)
{
    free(s);
}

bool StackEmpty(StackType *s)
{
    return s->top == -1;
}
bool Push(StackType *&s, ElemType e)
{
    if (s->top == MAXSIZE - 1)
        return false;
    s->top++;
    s->data[s->top] = e;
    return true;
}

bool Pop(StackType *&s, ElemType &e)
{
    if (s->top == -1)
        return false;
    e = s->data[s->top];
    s->top--;
    return true;
}

bool GetTop(StackType *s, ElemType &e)
{
    if (s->top == -1)
        return false;
    e = s->data[s->top];
    return true;
}

/*
共享栈,适用于一方可能用到满,另一方还有很多空间
栈空:top1 == -1   top2 == MAXSIZE
栈满:top1 ==top2-1
进栈出栈的时候top2的操作和top1是反的
*/

//********************************************************************
//一般,尾递归算法可以通过循环或者迭代转换为等价的非递归算法
int Fib2(int n)
{
    int a = 1, b = 1, i, s;
    if (n == 1 || n == 2)
        return 1;
    else
    {
        for (i = 3; i <= n; i++)
        {
            s = a + b;
            a = b;
            b = s;
        }
        return s;
    }
}

/*对于不是尾递归的复杂递归,可以在 理解递归调用实现过程 的基础上
用栈来模拟递归执行过程(这是关键啊),从而将其转换为等价的非递归算法*/
//汉诺塔问题的非递归实现:注意栈的结构决定了e1,e3的push顺序和实际执行顺序相反!

void Hanoi2(int n, char x, char y, char z)
{
    StackType *st;
    ElemType e, e1, e2, e3; //一共要用到这么多变量
    if (n <= 0)
        return;
    InitStack(st);
    e.n = n;
    e.x = x;
    e.y = y;
    e.z = z;
    Push(st, e);

    while (!StackEmpty(st))
    {
        Pop(st, e);
        if (e.flag == false)
        {
            e1.n = e.n - 1;
            e1.x = e.y;
            e1.y = e.x;
            e1.z = e.z;
            if (e1.n == 1)
                e1.flag = true;
            else
                e1.flag = false;
            Push(st, e1);

            e2.n = e.n;
            e2.x = e.x;
            e2.y = e.y;
            Push(st, e2);

            e3.n = e.n - 1;
            e3.x = e.x;
            e3.y = e.z;
            e3.z = e.y;
            if (e3.n == 1)
                e3.flag = true;
            else
                e3.flag = false;
            Push(st, e3);
        }
        else
            printf("将%d个盘片从%c移动到%c\n", e.n, e.x, e.z);
    }
    DestroyStack(st);
}

//递归算法执行中,最长的递归调用的链长称为该算法的递归调用深度
/*设计队规算法的基本步骤是先确定问题的递归模型,再转化成C/C++语言的函数
确定递归模型的步骤是:
1. 对原问题f(n)进行分析,假设出合理的小问题f(n-1)
2. 给出f(n)与f(n-1)甚至更多项之间的关系,也就是确定递归体,也
就是数学归纳法中的假设i = n-1成立,求证 i =n
3. 找到特定情况如f(1)作为递归出口 
4. 注意栈的次序和你的逻辑次序可能需要一定的调整*/


//有0~i个元素,求最小值,思路是找到前面的最小值
double Min(double A[], int i)
{
    double min;
    if (i == 0)
        return A[0];
    else
    {
        min = Min(A, i - 1);
        if (min > A[i])
            return A[i];
        else
            return min;
    }
}

5.数组

通常只有读和写两种操作。

每个元素占k个存储单元,则LOC(ai) = LOC(a1)+(i-1) x k

二维数组大家都是按行存放的,为啥要搞出按列来。

特殊矩阵压缩

对于ai,j = bk

  1. 对称矩阵 P169

    k = i(i+1)/2 + j i>=j k= j(j+1)/2 +i j>=i

  2. 下三角和对称矩阵十分相似,但是上三角还是有点差距的,最后一位有一个常数c P70

    上三角:k = i(2n-i+1)/2 +j-i i<=j n(n+1)/2 i>j

    下三角:k = i(i+1)/2 + j i>=j n(n+1)/2 i<j

  3. 三对角矩阵且存储到一维数组时,k = 2i+j b = 1

这样的压缩存储只需在算法中按公式作映射就可以实现随机存取

稀疏矩阵

非零元素具有随机性,用三元组表,十字链表比较复杂来存取

#include <stdio.h>
#include <malloc.h>
#define M 100       //行数
#define N 200       //列数
#define MaxSize 100 //假设最多只有100个非零项
typedef int ElemType;
typedef struct
{
    int r;      //行号
    int c;      //列号
    ElemType d; //元素值
} TupNode;
typedef struct
{
    int rows;
    int cols;
    int nums;
    TupNode data[MaxSize];
} TSMatrix;

//从二维稀疏矩阵创建三元组表示,有点新奇哈哈
void CreateMat(TSMatrix &t, ElemType A[M][N])
{
    int i, j;
    t.rows = M;
    t.cols = N;
    t.nums = 0;
    for (i = 0; i < M; i++)
        for (j = 0; j < N; j++)
            if (A[i][j] != 0)
            {
                t.data[t.nums].d = A[i][j];
                t.data[t.nums].r = i;
                t.data[t.nums].c = j;
                t.nums++;
            }
}

//三元组元素的赋值,不存在(0)时插入一个非零元素,存在时修改要理解好他这个数据结构是怎样的
bool Value(TSMatrix &t, ElemType x, int i, int j)
{
    int k = 0, k1;
    if (i < 0 || j < 0 || i >= t.rows || j >= t.cols)
        return false;
    while (k < t.nums && t.data[k].r < i)
        k++;
    while (k < t.nums && t.data[k].r == i && t.data[k].c < j)
        k++; //这个查找方式。。。秀啊
    if (t.data[k].r == i && t.data[k].c == j)
        t.data[k].d = x; //存在这样的非0元素
    else
    {
        for (k1 = t.nums - 1; k1 >= k; k1--) //这里注意带 =,不然第k项原本有意义的就被覆盖了
        {
            t.data[k1 + 1].r = t.data[k1].r;
            t.data[k1 + 1].c = t.data[k1].c;
            t.data[k1 + 1].d = t.data[k1].d;
        }
        t.data[k].r = i;
        t.data[k].c = j;
        t.data[k].d = x;
        t.nums++;//最终四步处理
    }
    return true;
}


bool Assign(TSMatrix t, ElemType &x, int i, int j)
{
    int k = 0;
    if (i < 0 || j < 0 || i >= t.rows || j >= t.cols)
        return false;
    while (k < t.nums && t.data[k].r < i)
        k++;
    while (k < t.nums && t.data[k].r == i && t.data[k].c < j)
        k++;
    if (t.data[k].r == i && t.data[k].c == j)
    {
        x = t.data[k].d;
        return true;
    }
    else
        x = 0; //返回0
    return false;
}

void DispMat(TSMatrix t)
{
    int k;
    if (t.nums <= 0)
        return;
    printf("\t%d\t%d\t%d\n", t.rows, t.cols, t.data);
    printf("\t------------------------\t");
    for (k = 0; k < t.nums; k++)
        printf("\t%d\t%d\t%d\n", t.data[k].r, t.data[k].c, t.data[k].d);
}

//转置,把列号放在前面,那就要依次查找列号,高效的是快速转置
void TranTat(TSMatrix t, TSMatrix &tb)
{
    int k, k1 = 0, v; //k1记录tb中的元素个数
    tb.rows = t.cols;
    tb.cols = t.rows;
    tb.nums = t.nums;
    if (t.nums != 0)
    {
        for (v = 0; v < t.cols; v++) //每一列都完整的遍历一遍,效率巨低。
            for (k = 0; k < t.nums; k++)
                if (t.data[k].c == v)
                {
                    tb.data[k1].r = t.data[k].c;
                    tb.data[k1].c = t.data[k].r;
                    tb.data[k1].d = t.data[k].d;
                    k1++;
                }
    }
}

//接下来是十字链表的数据结构P178
#define Max ((M) > (N) ? M : N)
typedef struct mtxn
{
    int row;
    int col;
    struct mtxn *right, *down;  //向右循环的行指针和向下循环的列指针
    union
    {
        ElemType value;
        struct mtxn *link; //指向下个头节点
    } tag;

} MatNode;

//代价是运算算法比较复杂

广义表

广义表的特征:

  1. 广义表中的数据元素是有相对次序的
  2. 长度定义为最外层包含元素的个数
  3. 深度定义为包含括弧的重数,原子的深度为0,空表的深度为1
  4. 广义表可以共享,这种叫再入表
  5. 广义表可以是一个递归表,一个广义表可以是自己的子表,这种叫递归表,深度∞,长度有限

不讨论再入表和递归表,小写字母表示原子,大写字母表示广义表的表名

没有给出表明的叫做匿名表 ,用 · 表示

表头:head(GL)叫做表头,取第一个元素,tail(GL)为其余所有部分(包括原来的括号),显然一个广义表的表尾始终为一个广义表,空表无表头表尾:

A()无表头表尾

B(e) 表头e 表尾() 空的也会被算上!!

C(a,· (b,c,d)) 表头 a 表尾((b,c,c))注意有两层括号,看来表尾会保持原有的深度

#include <stdio.h>
#include <malloc.h>
typedef int ElemType;
typedef struct lnode
{
    int tag; //标识符 0为原子节点,1为表节点
    union
    {
        ElemType data;         //存放数据值
        struct lnode *sublist; //指向子表的指针
    } val;
    struct lnode *link; //同一层的下一个元素
} GLNode;

//tag = 1的节点可以看成是一个单链表的头节点,指向子表的首节点,通过递归性,有两种解法
//解法1,把整个看成一个带头节点的单链表,种类分原子和子表,子表类似整个表,而原子则仅仅是原子处理罢了。
void fun1(GLNode *g)
{
    GLNode *gl = g->val.sublist;
    while (gl != NULL)
    {
        if (gl->tag == 1)
            fun1(gl);//z
        else
        {
            printf("原子处理语句");
        }
        gl = gl->link; //处理后继元素
    }
}

//解法2,对于元素节点,其兄弟域的节点和整个广义表是相似的,对于表节点,其元素域和兄弟域的处理均与整个广义表相似
void fun2(GLNode *g)
{
    if (g != NULL)
    {
        if (g->tag == 1)          //为子表
            fun2(g->val.sublist); //先递归处理表节点的元素域
        else
        {
            printf("原子处理语句");
        }
        fun2(g->link); //处理两种节点的兄弟(不用分类,因为无论什么节点,兄弟都相似于整个表)
    }
}
//实际问题中根据求解问题的特点自行选择其中来设计递归求解,比如计数:
int Count1(GLNode *g)
{
    int n = 0;
    GLNode *gl = g->val.sublist;
    while (gl != NULL)
    {
        if (gl->tag == 0)
            n++;
        else
            n += Count1(gl->val.sublist);
        gl = gl->link;
    }
    return n;
}

int Count2(GLNode *g)
{
    int n = 0;
    GLNode *gl = g->val.sublist;
    while (gl != NULL)
    {
        if (gl->tag == 1)
            n += Count2(gl->val.sublist);
        else
            n++;
        n += Count2(g->link);
    }
    return n;
}



int GLLength(GLNode *g)
{
    int n = 0;
    GLNode *gl = g->val.sublist;
    while (g != NULL)
    {
        n++;
        gl = gl->link; //只用算最表层的哈哈
    }
    return n;
}

int GLDepth(GLNode *g)
{
    GLNode *gl;
    int maxd = 0, dep; //maxd是同一层子表中深度的最大值
    if (g->tag == 0)
        return 0;
    gl = g->val.sublist;
    if (gl == NULL)
        return 1;

    //这下面这个操作还是挺妙的
    while (gl != NULL)
    {
        if (gl->tag == 1)
        {
            dep = GLDepth(gl->val.sublist); //dep代表该节点的深度!自顶向下
            if (dep > maxd)
                maxd = dep;
        }
        gl = gl->link;
    }
    return (maxd + 1); //返回这一层的最大值到上一层的节点
}

//要输出成括号的形式还是有点麻烦的,元素直接输出值,子表则输出 ‘(’,空表输出‘#’,非空子表递归,再输出‘)’
//这里其实用到的就是递归思想,仅考虑一层。非常简单
void DispGL(GLNode *g)
{
    if (g != NULL)
    {
        if (g->tag == 0)
            printf("%c", g->val.data);
        else
        {
            printf("(");
            if (g->val.sublist == NULL)
                printf("#");
            else
                DispGL(g->val.sublist);
            printf(")"); //这个括号不要漏了
        }
        if (g->link != NULL)
        {
            printf(",");
            DispGL(g->link); //兄弟应该在子表后面输出
        }
    }
}

//与输出对应的,建立广义表的链式存储结构,记得空表是 “#”,遇到则将g->val.sublist置空
//扫描到 ( 则用g递归开启一个子表节点,遇到 )说明已经处理完,g置空
//时空复杂的均为O(n)
//切记把链式结构和符号表示分开想,不要混在一起!

//巧妙地递归逻辑,配合引用参数(注意这里默认一个元素是‘单个’字符)
//这里我一开始理解难受是因为它既有嵌套递归,又有把子任务视为平级的递归,害。
GLNode *CreateGL(char *&s)
{
    GLNode *g;
    char ch = *s++; //取一个字符,直接操作指针的话,便于后续调用
    if (ch != '\0')
    {
        g = (GLNode *)malloc(sizeof(GLNode));
        if (ch == '(')
        {
            g->tag = 1;
            g->val.sublist = CreateGL(s);
        }
        else if (ch == ')')
            g = NULL;
        else if (ch == '#')
            g = NULL;
        else
        {
            g->tag = 0;
            g->val.data = ch;
        }
    }
    else
        g = NULL; //若s扫描完,把g置空
    ch = *s++;
    if (g != NULL)
    {
        if (ch == ',')
            g->link = CreateGL(s);
        else
            g->link = NULL;
    }
    return g;
}

//采用解法1,递归销毁,注意要灵活一点,顺序什么的不影响那么怎样方便怎样来
void DestroyGL(GLNode *&g)
{
    GLNode *g1, *g2;
    g1 = g->val.sublist;
    while (g1 != NULL)
    {
        if (g1->tag == 0)
        {
            g2 = g1->link; //保存兄弟节点
            free(g1);
            g1 = g2;
        }
        else
        {
            g2 = g1->link;
            DestroyGL(g1->val.sublist);
            g1 = g2;
        }
    }
    free(g);
}

6.树

基本和存储

有树形表示法,文氏图表示法,凹入表示法和括号表示法等等。

树的定义是递归的,树的性质在P192

  1. 结点数 = 所有节点的出度(其实就是边数) + 1
  2. 度为m的树(就是节点中最大出度为m)中第i层上最多有m^(i-1)个节点,若每一层都是满的,称为满m次树
  3. 高度为h的m次树最多有(m^h - 1)/(m-1) 个节点
  4. 具有n个结点的m次树的最小高度为logm(n(m-1)+1)向大取整

其实性质2,3,4说的都是一种情况。。。

对于m次树,含有n个节点,那么最大高度maxh = n - (m-1) 显然,除了最后一个节点,其他度都为1就好了

基本运算有寻找特定节点,插入/删除特定节点,遍历

遍历的先中后原来是根节点的输出次序,一般都习惯先左后右。

//存储结构
#include <stdio.h>
#include <malloc.h>
//1.双亲存储,顺序存储,根节点的父节点设置为-1,其余设置为父节点在顺序中的位置
//求双亲容易,但是求某个节点的子节点难,要遍历整个存储结构
#define MaxSize 100 //假设最多只有100个非零项
#define MaxSons 100 //假设树的度为100
typedef int ElemType;
typedef struct
{
    ElemType data;
    int parent;
} PTree[MaxSize];

//2.子链存储,每个节点有指向所有孩子结点的指针,因为难以确定数目,统一用最大的【树的度】来分配
//找双亲费时,找孩子很方便,树的度与平均度偏差较大时,浪费

#define MaxSons 100 //假设树的度为100
typedef struct node
{
    ElemType data;
    struct node *sons[MaxSons];
} TSonNode;
//以此为基础求树的高度
int TreeHeight1(TSonNode *t)
{
    TSonNode *p;
    int i, h, maxh = 0;
    if (t == NULL)
        return 0;
    else
    {
        for (i = 0; i < MaxSons; i++)
        {
            p = t->sons[i];
            if (p != NULL)
            {
                h = TreeHeight1(p); //求子树的深度
                if (maxh < h)
                    maxh = h;
            }
        }
    }
    return (maxh + 1);
}

//3.孩子兄弟链,每个节点三个域,一个元素,一个指向长子,一个指向兄弟
typedef struct tnode
{
    ElemType data;
    struct tnode *hp; //指向兄弟 horizontal point
    struct tnode *vp; //指向孩子 vertical point
} TSBNode;
//其实这种结构是把树转换为二叉树的存储结构
//最大的优点就是方便的实现树和二叉树的相互转换,缺点时查找父节点麻烦

int TreeHeight2(TSBNode *t)
{
    TSBNode *p;
    int h, maxh = 0;
    if (t == NULL)
        return 0;
    p = t->vp;
    while (p != NULL) //遍历同一层的p
    {
        h = TreeHeight2(p->vp); //求出这一层的最大值
        if (maxh < h)
            maxh = h;
        p = p->hp;
    }
    return maxh + 1;
}

二叉树

呜呜呜,好长这里,冲冲冲!

二叉树是区分左右的,任何m次数都可以转化为二叉树结构

满二叉树:叶子节点都集中在二叉树的最下一层,所有分支节点都有左右孩子的树,只有度为0或2的节点。

可以进行层序编号,第一个是0,若节点为x,则左孩子为2x,右孩子为2x + 1

完全二叉树:只有最下两层节点度数可以小于2,且叶子节点全都靠左排列。

最多只有1个出度为1的节点,且节点有左孩子,结点总数n为奇数时没有出度为1的结点

满二叉树是完全二叉树的特例

二叉树性质:

  1. 非空二叉树上的叶子节点数等于双分支结点数+1

    用到了:m(度的和) = n-1 度的和 = n1 + 2n2 n = n0(叶子结点数) + n1 + n2

  2. 非空二叉树的第i层最多只有2^(i-1)个结点

  3. 高度为h的二叉树最多有 2^h - 1 个结点

  4. 若节点为x,则左孩子为2x,右孩子为2x + 1,父节点为x/2 向下取整

  5. 具有n个节点的完全二叉树高度为 log2(n+1)向上取整 或者log2n向下取整 +1

树转二叉树:相邻兄弟加线,保留长子线,其余删除 森林则把根节点链接

二叉树转树:若某节点为双亲的左孩子,则把该节点的右孩子,右孩子的右孩子都与该节点的双亲连起来,

删除原二叉树中所有双亲结点与右孩子结点之间的连线。

#include <stdio.h>
#include <malloc.h>
/*先看看顺序存储结构
对于完全二叉树和满二叉树,可以实现随机存储,完全二叉树最后几个空的用 # 表示
对于一般的二叉树,可以增添一些不存在的空结点,使之成为一棵完全二叉树的形式。
图可以看看P204  */
#define MaxSize 100 //假设最多只有100个非零项
typedef int ElemType;
typedef ElemType SqBinTree[MaxSize];
//但是如果空的太多,会造成空间的大量浪费,但是查找子节点和父节点都很方便。
//当然还有一般顺序存储结构的缺点,就是插入,删除很不方便

//链式存储,称为二叉链,用根节点指针b唯一标识整个存储结构,称为二叉树b
typedef struct node
{
    ElemType data;
    struct node *lchild;
    struct node *rchild;
    //struct node *parent;
} BTNode;
//这样节省空间,但不方便访问父节点,可以增加一个指向父节点的指针域parent来解决。
//后续一般假设一棵二叉树中所有结点值均不同,为单个字符
//创建,销毁,按值查找(父),找孩子,求高度,输出   创建和输出都用括号表示法

//首先得理解二叉树的括号表示,模拟一个栈来实现,因为栈的实现很简单,并没有封装
//这个用栈保存双亲结点的思想太妙了!栈顶存放的是当前处理节点的父节点
void CreateBTree(BTNode *&b, char *str)
{
    BTNode *St[MaxSize], *p; //St为顺序栈
    int top = -1, k, j = 0;  //j为str索引,k用来判断孩子类型
    char ch;
    b = NULL;
    ch = str[j];
    while (ch != '\0')
    {
        switch (ch)
        {
        case '(':
            top++;
            St[top] = p;
            k = 1;
            break;
        case ')':
            top--;
            break;
        case ',':
            k = 2;
            break;
        default:
            p = (BTNode *)malloc(sizeof(BTNode));
            p->data = ch;
            p->lchild = p->rchild = NULL;
            if (b == NULL)
                b = p; //这是只存在一次的还未建立根节点的情况
            else
            {
                switch (k)//只有1和2的情况来回切换!
                {
                case 1:
                    St[top]->lchild = p;
                    break;
                case 2:
                    St[top]->rchild = p;
                    break;
                }
            }
        }
        j++;
        ch = str[j];
    }
}

void DestroyBTree(BTNode *&b)
{
    if (b != NULL)
    {
        DestroyBTree(b->lchild);
        DestroyBTree(b->rchild);
        free(b);
    }
}

//查找x的结点
BTNode *FindeNode(BTNode *b, ElemType x)
{
    BTNode *p;
    if (b == NULL)
        return NULL;
    else if (b->data == x)
        return b;
    else
    {
        p = FindeNode(b->lchild, x);
        if (p != NULL)
            return p;
        else
            return FindeNode(b->rchild, x);
        //p
        //这也把没找到的情况包含在内了,要学会简化代码
    }
}

BTNode *LchildNode(BTNode *p)
{
    return p->lchild;
}

BTNode *RchildNode(BTNode *p)
{
    return p->rchild;
}

//求高度
int BTHeight(BTNode *b)
{
    int lchild, rchild;
    if (b == NULL)
        return 0;
    else
    {
        lchild = BTHeight(b->lchild);
        rchild = BTHeight(b->rchild);
    }
    return (lchild > rchild ? lchild : rchild) + 1;
}

//终究还是比广义表简单不少啊,当是NULL的时候不操作,非常流弊
void DispBTree(BTNode *b)
{
    if (b != NULL)
    {
        printf("%c", b->data);
        if (b->lchild != NULL || b->rchild != NULL)
        {
            printf("(");
            DispBTree(b->lchild);
            printf(",");
            DispBTree(b->rchild);
            printf(")");
        }
    }
}

int main()
{
    //突然想玩一下下
    char str[MaxSize] = "A(B(D(,G))C(E,F))";
    BTNode *BT;
    CreateBTree(BT, str);
    DispBTree(BT);
    printf("\nI prefer Python, C is to complex and confused");
}

二叉树遍历


/*遍历,四种顺序,层次遍历是非递归的
一颗二叉树可以分为根节点和子树两类,根节点直接处理,子树递归处理
如果必须先处理子树,那就用后序,如果必须先处理根节点,那就用先序,否则随便
如果要区分左,右树,那就要考虑中序,但是比较少*/
void Preorder(BTNode *b)
{
    if (b != NULL)
    {
        printf("%c", b->data);
        Preorder(b->lchild);
        Preorder(b->rchild);
    }
}

void Inorder(BTNode *b)
{
    if (b != NULL)
    {
        Inorder(b->lchild);
        printf("%c", b->data);
        Inorder(b->rchild);
    }
}

void PostOrder(BTNode *b)
{
    if (b != NULL)
    {
        PostOrder(b->lchild);
        PostOrder(b->rchild);
        printf("%c", b->data);
    }
}

//求给定二叉树的所有结点个数
int Nodes(BTNode *b)
{
    if (b == NULL)
        return 0;
    else
        return Nodes(b->lchild) + Nodes(b->rchild) + 1;
    //这个是先左后右后根,是后续遍历
}
//输出所有的叶子节点
/*递归模型很重要!
f(b) === 不做任何事情    b=NULL
f(b) === 输出b的data域   b为叶子结点
f(b) === f(b->lchild);f(b->rchild) 其他情况*/
void DispLeaf(BTNode *b)
{
    if (b != NULL)
    {
        if (b->lchild == NULL && b->rchild == NULL)
            printf("%c", b->data);
        DispLeaf(b->lchild);
        DispLeaf(b->rchild);
    }
}

//求深度,h置处置1,这个和之前那个稍微有点不同,具体分析
int Level(BTNode *b, ElemType x, int h = 1)
{
    int l;
    if (b == NULL)
        return 0;
    else if (b->data == x)
        return h;
    else
    {
        l = Level(b->lchild, x, h + 1);
        if (l)
            return l;
        else
            return (Level(b->rchild, x, h + 1)); //又见到了这种操作
    }
}

//求b树第k层的结点数,h是用来传递参数,初始为1
//这是另一种递归思路,并没有回带的过程,利用了void和引用
//如果在c语言中,可以用全局变量来代替所谓的引用。
void Lnodenum(BTNode *b, int k, int h = 1, int &n)
{
    if (b == NULL)
        return;
    else
    {
        if (k == h)
        {
            n++;
        }
        else if (h < k)
        {
            Lnodenum(b->lchild, h + 1, k, n);
            Lnodenum(b->rchild, h + 1, k, n);
        }
    }
}

//输出x的结点的所有祖先,多判断一级的思维
bool ancestor(BTNode *b, ElemType x)
{
    if (b == NULL)
        return false;
    else if (b->lchild != NULL && b->lchild->data == x || b->rchild != NULL && b->rchild->data == x)
    {
        putchar(b->data);
        return true;
    }
    else if (ancestor(b->lchild, x) || ancestor(b->rchild, x))
    {

        putchar(b->data);
        return true;
    }
    else
        return false;
}

//非递归算法区域P218
//层次遍历算法
typedef struct
{
    BTNode *data[MaxSize];
    int front, rear;
} SqQueue;

void LevelOrder(BTNode *b)
{
    BTNode *p;
    SqQueue *qu;
    enqueue(qu, b);
    while (!QueueEmpty(qu))
    {
        deQueue(qu, p);
        putchar(p->data);
        if (p->lchild != NULL)
            enqueue(qu, p->lchild);
        if (p->rchild != NULL)
            enqueue(qu, p->rchild);
    }
}

二叉树构造

由先序序列+中序序列,或者中序序列+后序序列唯一地确定一颗二叉树

实际上,先序序列的作用是确定一棵二叉树的根节点,中序序列的作用是确定左、右子树的中序序列,从而进一步确定先序序列,递归构造左右子树。

原理在P229


//二叉树的构造
//pre存放先序序列,in存放中序序列,n为二叉树的节点个数
//其实函数的参数选择同时得考虑递归时不同层级间需要的参数
BTNode *CreateBT1(char *pre, char *in, int n)
{
    BTNode *b;
    char *p;
    int k;
    if (n <= 0)
        return NULL;
    b = (BTNode *)malloc(sizeof(BTNode));
    b->data = *pre; //根节点
    for (p = in; p < in + n; p++)
        if (*p == *pre)
            break;
    k = p - in; //确定根节点在in中的位置
    b->lchild = CreateBT1(pre + 1, in, k);
    b->rchild = CreateBT1(pre + k + 1, p + 1, n - k - 1);
    //这里不能光考虑第一次的,要用通用的写法
    return b;
}

//和上面基本一致
BTNode *CreateBT2(char *post, char *in, int n)
{
    BTNode *b
    char r, *p; //因为根节点在后头,所以得用一个变量单独保存
    int k;
    if (n <= 0)
        return NULL;
    b = (BTNode *)malloc(sizeof(BTNode));
    r = *(post + n - 1);
    b->data = r;
    for (p = post; p < post + n; p++)
    {
        if (*p == r)
            break;
    }
    k = p - post;
    b->lchild = CreateBT2(post, in, k);
    b->rchild = CreateBT2(post + k, p + 1, n - k + 1);
    return b;
}

哈夫曼

WPL Weighted Path Length 带权路径长度

定理:假如哈夫曼树有n个叶子结点,那么一共有2n+1个


//哈夫曼树采用数组存放,总的结点数可以算出来 P239
//前n个存放原结点(叶子结点),剩下的存放分支节点。
//思路是先将全部节点的parent、lchild、rchild赋值为-1,然后不断找最小的放在后面,同时补全信息
typedef struct
{
    char data;
    double weight;
    int parent;
    int lchild;
    int rchild;
} HTNode;

void CreateHT(HTNode ht[], int n0)
{
    int i, k, lnode, rnode;
    double min1, min2;
    for (i = 0; i < 2 * n0 + 1; i++)
        ht[i].parent = ht[i].lchild = ht[i].rchild = -1;
    for (i = n0; i < 2 * n0 - 1; i++)
    {
        min1 = min2 = 32767;
        lnode = rnode = -1;
        for (k = 0; k < i - 1; k++)
            //通过确定两个最小值的相对次序来巧妙地处理该问题
            if (ht[k].parent == -1)
            { //在尚未构造二叉树的结点中查找
                if (ht[k].weight < min1)
                {
                    min2 = min1;
                    rnode = lnode;
                    min1 = ht[k].weight;
                    lnode = k;
                }
                else if (ht[k].weight < min2)
                {
                    min2 = ht[k].weight;
                    rnode = k;
                }
            }
        ht[i].weight = ht[lnode].weight + ht[rnode].weight;
        ht[i].lchild = lnode;
        ht[i].rchild = rnode;
        ht[lnode].parent = i;
        ht[rnode].parent = i;
    }
}

//哈夫曼编码,规定左分支为0,右分支为1
#define N 100
typedef struct
{
    char cd[N]; //存放当前节点的哈夫曼编码
    int start;  //表明cd[start..n0]部分是哈夫曼编码
} HCode;

//这是个从下向上的过程,但是最终是顺序的
void CreateHCode(HTNode ht[], HCode hcd[], int n0)
{
    int i, f, c;
    HCode hc;
    for (i = 0; i < n0; i++)
    {
        hc.start = n0;
        c = i;
        f = ht[i].parent; //初始化
        while (f != -1)
        { //循环到根节点
            if (ht[f].lchild == c)
                hc.cd[hc.start--] = '0';
            else
                hc.cd[hc.start--] = '1';
            c = f;
            f = ht[f].parent;
        }
        hc.start++; //因为多减了一次,加回来
        hcd[i] = hc;
    }
}

7.图

找强连通分量:先找一个有向环,如果外面的某个顶点到该环任意结点均有双向路径,则加入

带权图也称作网

矩阵、表存储

#define MAXV 100  //最大结点数
#define INF 32767 //所谓的正无穷,也就是没有路
#include <stdio.h>
#include <stdlib.h>
typedef int InfoType;
typedef struct
{
    int no;        //顶点编号
    InfoType info; //其他信息
} VertexType;

typedef struct
{
    int edges[MAXV][MAXV]; //邻接矩阵数组
    int n, e;              //顶点数、边数
    VertexType vexs[MAXV]; //存放结点信息
} MatGraph;

/*适合储存边比较多的稠密图
邻接矩阵表示是唯一的,注意无向图、有向图每一行/列的意义
非常适合提取两个顶点之间的边,适用于该目的的算法*/

//邻接表结构是后续众多算法的基础,一定要弄清楚P259
//每个顶点一个链表,链接关联的边。其中的每个边结点表示一条!边!的信息,not 点
//头节点存储的则是顶点的信息,并指向首节点。
typedef struct ANode
{
    int adjvex;            //该边的临界点编号,指的是出边临界点
    struct ANode *nextarc; //指向下个边的指针
    int weight;            //该边的相关信息
} ArcNode;

typedef struct Vnode
{
    InfoType info;
    ArcNode *firstarc;
} VNode;

typedef struct
{
    VNode adjlist[MAXV]; //头节点数组
    int n, e;
} AdjGraph;
//还有所谓的逆邻接表
/*邻接表的表示不唯一,取决于算法和输入次序,适合边数目稀疏的图
对于无向图,第i个单链表的边数目是顶点i的度,有向图则为出度,入度得统计所有的adjvex域为i的数目
适合提取某个顶点的所有临界点*/

//依次扫描,头插法插入。
void CreateAdj(AdjGraph *&G, int A[MAXV][MAXV], int n, int e)
{
    int i, j;
    ArcNode *p;
    for (i = 0; i < n; i++)
        G->adjlist[i].firstarc = NULL; //初始化
    for (i = 0; i < n; i++)
        for (j = n - 1; j >= 0; j--)
            if (A[i][j] != 0 && A[i][j] != INF)
            {
                p = (ArcNode *)malloc(sizeof(ArcNode));
                p->adjvex = j;
                p->weight = A[i][j];
                p->nextarc = G->adjlist[i].firstarc;
                G->adjlist[i].firstarc = p;
            }
    G->n = n;
    G->e = e;
}

//输出规则为先输出头结点的定点信息,再依次输出所有结点的顶点编号
void DispAdj(AdjGraph *G)
{
    int i;
    ArcNode *p;
    for (i = 0; i < G->n; i++)
    {
        p = G->adjlist[i].firstarc;
        printf("%3d:", i);
        while (p != NULL)
        {
            printf("%3d[%d]", p->adjvex, p->weight);
            p = p->nextarc;
        }
        printf("\n");
    }
}

void DestroyAdj(AdjGraph *&G)
{
    int i;
    ArcNode *pre, *p;
    for (i = 0; i < G->n; i++)
    {
        pre = G->adjlist[i].firstarc;
        if (pre != NULL)
        {
            p = pre->nextarc;
            while (p != NULL)
            {
                free(p);
                pre = p;
                p = pre->nextarc;
            }
            free(pre);
        }
    }
    free(G); //别忘了把头节点数组也释放掉
}

//将邻接表转换为邻接矩阵
void ListToMat(AdjGraph *&G, MatGraph &g)
{
    int i;
    ArcNode *p;
    for (i = 0; i < G->n; i++)
    {
        p = G->adjlist[i].firstarc;
        while (p != NULL)
        {
            g.edges[i][p->adjvex] = p->weight;
            p = p->nextarc;
        }
    }
    g.n = G->n;
    g.e = G->e;
}

/*十字链表
是给有向图用的,是邻接表和逆邻接表的结合,现在看其实非常简单。。不知道当时为啥讲那么久。*/

图遍历及应用

//图的遍历,要求每个顶点仅被访问一次。
//因为图不像树,存在回路,得设置一个访问标记数组visited,当访问过时置1,否则为0

//深度优先Depyh First Search DFS
int visited[MAXV] = {0}; //全局置0,从v开始遍历
void DFS(AdjGraph *G, int v)
{
    ArcNode *p;
    visited[v] = 1;
    printf("%2d", v);
    p = G->adjlist[v].firstarc;
    while (p != NULL)
    {
        if (visited[p->adjvex] == 0)
            DFS(G, p->adjvex);
        p = p->nextarc;
    }
}

//广度优先算法Breadth First Search BFS,显然这种结构要用到队列
#include "linked_queue.cpp"
void BFS(AdjGraph *G, int v)
{
    int w, i;
    ArcNode *p;
    LinkQuNode *qu;
    InitQueue(qu);
    int visited[MAXV];
    for (i = 0; i < G->n; i++)
        visited[i] = 0;
    printf("%2d", v);
    visited[v] = 1;
    enqueue(qu, v);
    while (!QueueEmpty(qu))
    {
        dequeue(qu, w);
        p = G->adjlist[w].firstarc;
        while (p != NULL)
        {
            if (visited[p->adjvex] == 0)
            {
                printf("%2d", p->adjvex);
                visited[p->adjvex] = 1;
                enqueue(qu, p->adjvex);
            }
            p = p->nextarc;
        }
    }
    printf("\n");
}

//遍历非连通图
void DFS1(AdjGraph *G)
{
    int i;
    for (i = 0; i < G->n; i++)
        if (visited[i] == 0)
            DFS(G, i);
}

void BFS1(AdjGraph *G)
{
    int i;
    for (i = 0; i < G->n; i++)
        if (visited[i] == 0)
            BFS(G, i);
}

bool Connect(AdjGraph *G)
{
    int i;
    bool flag = true;
    for (i = 0; i < G->n; i++)
        visited[i] = 0;
    DFS(G, 0);
    for (i = 0; i < G->n; i++)
        if (visited[i] == 0)
        {
            flag = false;
            break;
        }
    return flag;
}

//图遍历算法的应用

//深度搜索判断是否存在路径。
void ExitPath(AdjGraph *G, int u, int v, bool &has)
{
    int w;
    ArcNode *p;
    visited[u] = 1;
    if (u == v)
    {
        has = true;
        return;
    }
    p = G->adjlist[u].firstarc;
    while (p != NULL)
    {
        w = p->adjvex;
        if (visited[w] == 0)
            ExitPath(G, w, v, has);
        p = p->nextarc;
    }
}

//输出从u到v的一条路径,假设已知u到v间有路径。
//只要正常遍历就好,绝对会输出一条路径,不过非常随机就是了。
void FindaPath(AdjGraph *G, int u, int v, int path[], int d)
{
    //d表示path中的路径长度,初始为-1
    int w, i;
    ArcNode *p;
    visited[u] = 1;
    d++;
    path[d] = u;
    if (u == v)
    {
        for (i = 0; i <= d; i++)
        {
            printf("%d", path[i]);
            printf("\n");
            return;
        }
    }
    p = G->adjlist[u].firstarc;
    while (p != NULL)
    {
        w = p->adjvex;
        if (visited[w] == 0)
            FindaPath(G, w, v, path, d);
        p = p->nextarc;
    }
}

//这个递归还是让人想了一会
void FindALLPath(AdjGraph *G, int u, int v, int path[], int d)
{

    //d表示path中的路径长度,初始为-1
    int w, i;
    ArcNode *p;
    visited[u] = 1;
    d++;
    path[d] = u;
    if (u == v)
    {
        for (i = 0; i <= d; i++)
        {
            printf("%d", path[i]);
            printf("\n");
            return;
        }
    }
    p = G->adjlist[u].firstarc;
    while (p != NULL)
    {
        w = p->adjvex;
        if (visited[w] == 0)
        {
            FindALLPath(G, w, v, path, d);
            p = p->nextarc; //递归体的核心就在这里
        }
    }
    visited[u] = 0; //恢复环境,可以重复利用。
}

//后面的广度优先算法的应用暂时没有更新

最小生成树

图的最小生成树是树的所有生成树中边上权值最小的。(实际上是选节点)

  • n个顶点的话就有n - 1条边。
  • 必须只使用该图中的边来构造
  • 不能使用会产生回路的边。

这个应用有很多,求最小生成树的算法:Prim 、克鲁斯卡尔

只要遍历一次,就能得到生成树,分为深度优先生成树和广度优先生成树。

普利姆算法

P281 依次选择最小边,因为需要频繁取边,所以图采用邻接矩阵更合适

这体现出了从算法逻辑设计到代码实现的过程也并非易事的,需要一些巧妙的构思

// Prim算法
//建议先阅读一下P284,搞清楚lowcost(到U中的最小边)和closet(最小边对应的顶点),这种实时更新最值的思想很有用,简化了算法
void Prim(MatGraph g, int v)
{
    int lowcost[MAXV];
    int MIN;
    int closet[MAXV], i, j, k;
    for (i = 0; i < g.n; i++)
    {
        lowcost[i] = g.edges[v][i];
        closet[i] = v;
        //初始化
    }
    for (i = 1; i < g.n; i++) //找出n - 1个顶点
    {
        MIN = INF;
        for (j = 0; j < g.n; j++)
            if (lowcost[j] != 0 && lowcost[j] < MIN)
            {
                MIN = lowcost[j];
                k = j; //k记录最小边的顶点编号
            }
        printf("边(%d,%d)权为%d\n", closet[k], k, MIN); //输出最小生成树的一条边
        lowcost[k] = 0;                                 //标记k已经加入U中
        for (j = 0; j < g.n; j++)
            if (lowcost[j] != 0 && g.edges[k][j] < lowcost[j])
            {
                lowcost[j] = g.edges[k][j];
                closet[j] = k;
            }
    }
}

克鲁斯卡尔算法

这才是正儿八经的选边。P285

当一个图有多个最小生成树时,这两个算法的求解结果不一定是相同的。

因为同样要频繁取边,也采用邻接矩阵来处理。

//关键在于如何判断选取一条边(i,j)加入到T中是否出现回路,可以通过判断顶点i,j是否同属于一个连通分量来解决
//利用vset[0..n-1]数组来完成,太厉害了,i和j处理时,改成i或j的vset值都可以

// Prim算法
//建议先阅读一下P284,搞清楚lowcost(到U中的最小边)和closet(最小边对应的顶点),这种实时更新最值的思想很有用,简化了算法
void Prim(MatGraph g, int v)
{
    int lowcost[MAXV];
    int MIN;
    int closet[MAXV], i, j, k;
    for (i = 0; i < g.n; i++)
    {
        lowcost[i] = g.edges[v][i];
        closet[i] = v;
        //初始化
    }
    for (i = 1; i < g.n; i++) //找出n - 1个顶点
    {
        MIN = INF;
        for (j = 0; j < g.n; j++)
            if (lowcost[j] != 0 && lowcost[j] < MIN)
            {
                MIN = lowcost[j];
                k = j; //k记录最小边的顶点编号
            }
        printf("边(%d,%d)权为%d\n", closet[k], k, MIN); //输出最小生成树的一条边
        lowcost[k] = 0;                                 //标记k已经加入U中
        for (j = 0; j < g.n; j++)
            if (lowcost[j] != 0 && g.edges[k][j] < lowcost[j])
            {
                lowcost[j] = g.edges[k][j];
                closet[j] = k;
            }
    }
}

//克鲁斯卡尔
typedef struct
{
    int u;
    int v;
    int w;
    //起始顶点,终止顶点和权值
} Edge;
#define Maxsize 20000 //最大边数
void Kruskal(MatGraph g)
{
    int i, j, u1, v1, sn1, sn2, k;
    int vset[MAXV];
    Edge E[Maxsize]; //存放所有的边
    k = 0;           //e数组的下标
    //第一步,由g产生E
    for (i = 0; i < g.n; i++)
        for (j = 0; j < g.n; j++)

            if (g.edges[i][j] != 0 && g.edges[i][j] != INF)
            {
                E[k].u = i;
                E[k].v = k;
                E[k].w = g.edges[i][j];
                k++;
            }
    //InsertSort(E,g.e)//对所有的边按照权值排序
    for (i = 0; i < g.n; i++)
        vset[i] = i; //初始化辅助数组
    k = 1;           //表示当前构造生成树的第几条边,初始为1
    j = 0;           //E中边的下标,初始为0

    //主体
    while (k < g.n) //生成树有n-1条边
    {
        u1 = E[j].u;
        v1 = E[j].v;
        sn1 = vset[u1];
        sn2 = vset[v1];
        if (sn1 != sn2)
        {
            printf("(%d,%d):%d\n", u1, v1, E[j].w); //输出一条边
            k++;
            for (i = 0; i < g.n; i++)
                if (vset[i] == sn2)
                    vset[i] = sn1;
        }
        j++;
    }
}

最短路径

突然想研究一下能不能上传文件

广东省2021招生计划

haha,那我到时候直接把源代码传上来好了,免得这个复制太难弄了

//不想做了。。狄克斯特拉算法和弗洛伊德算法,反正只考概念罢了

拓扑排序

AOV

AOV网,用顶点表示活动,用有向边表示活动之间优先关系的有向图称为AOV网(顶点表示活动的网)

选择没有前驱的顶点输出,删去该顶点以及该顶点发出的所有边,重复以上二步

若全部顶点被输出,则不存在回路,否则存在回路

AOE

顶点表示事件,有向边表示活动,开始事件(源点),结束事件(汇点)

从源点到汇点所有路径中具有最大路径长度的路径称为关键路径

建议看看P304

先进行一次拓扑排序,然后递归从头找最大,递归从尾找最小。

8.查找

线性表

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

//线性表分顺序和链式,只介绍顺序表,属于静态查找。
typedef int KeyType;
typedef struct
{
    char name[100];
    int years;
} InfoType;

typedef struct
{
    KeyType key;   //关键字
    InfoType data; //其他数据
} RecType;

//顺序查找,ASL成功 = (n+1)/2 ASL失败 = n
int SeqSearch(RecType R[], int n, KeyType k)
{
    int i = 0;
    while (R[i].key != k && i < n)
        i++;
    if (i >= n)
        return 0;
    else
        return i + 1; //逻辑值要加一
}
//从精简算法,提高查找速度的角度说,可以在R末尾增加一个关键字为k的记录为哨兵,就不用判断i是否超界
int SeqSearch1(RecType R[], int n, KeyType k)
{
    int i = 0;
    R[n].key = k;
    while (R[i].key != k)
        i++;
    if (i == n)
        return 0;
    else
        return i + 1;
}

//折半查找
//要求有序表,mid = (low+high)/2向下取整,成功返回逻辑序号,失败返回0
int BinSearch(RecType R[], int n, KeyType k)
{
    int low = 0, high = n - 1, mid;
    while (low <= high)
    {
        mid = (low + high) / 2;
        if (k == R[mid].key)
            return mid + 1;
        if (k < R[mid].key)
            high = mid - 1;
        else
            low = mid + 1;
    }
    return 0;
}
//好精简的算法!
//可以用判定树刻画,n种成功情况,成功时比较次数恰为层数和n+!种失败情况。失败时比较次数为层数-1
//ASLbn = log2(n+1)-1  最坏性能和平均性能相当接近,归纳起来复杂度为O(log2n)

//索引结构和分块查找
/*索引项一般为(关键字,地址),可以现在有序索引表中快速查找,然后通过地址找到
提高了查找效率,但是需要建立索引表会增加时空开销*/
#define MAXI 10000 //索引表最大容量。
typedef struct
{
    KeyType key;
    int link; //对应在存储表里的下标
} IdxType;

int IdxSearch(IdxType I[], int b, RecType R[], int n, KeyType k)
{
    int s = (n + b - 1) / b; //s为每块的元素个数,I的长度为b
    int low = 0, high = b - 1, mid, i;
    while (low <= high)
    {
        mid = (low + high) / 2;
        if (I[mid].key >= k)
            high = mid - 1;
        else
            low = mid + 1;
    }

    //接下来在该块中顺序查找
    i = I[high + 1].link; //记住是high+1,总会取到偏小的一个
    while (i < I[high + 1].link + s - 1 && R[i].key != k)
        i++;
    if (i <= I[high + 1].link + s - 1)
        return i + 1;
    else
        return 0;
}
/*折半查找配合时,成功查找的平均长度为ASLblk = ASLbn + ASLsq
= log2(b+1) -1 + (s+1)/2 = log2(n/s+1) + s/2 可见s即每块的长度越小越好*/
/*顺序查找时,ASLblk = ((b+1)+(s+1))/2 =1/2(n/s +s) +1 因为b = n/s向上取值,当s=根号n时最佳*/

树表

如果要进行表的删除、插入操作,会带来很多额外开销,若要对动态查找表进行查找,可以用几种树来。

二叉排序树的中序序列就是排好序的表

查找的ASL成功和ASL失败在P328

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

//假设各结点的关键字是唯一的
typedef int KeyType;
typedef struct
{
    char name[100];
    int years;
} InfoType;

typedef struct node
{
    KeyType key;
    InfoType data;
    struct node *lchild, *rchild;
} BSTNode;

//用根节点bt来唯一标识一棵二叉排序树

//插入关键字k,若已有则返回假
bool InsertBST(BSTNode *&bt, KeyType k)
{
    if (bt == NULL)
    {
        bt = (BSTNode *)malloc(sizeof(BSTNode));
        bt->key = k;
        bt->lchild = bt->rchild = NULL;
        return true;
    }
    else if (k == bt->key)
        return false;
    else if (k < bt->key)
        return InsertBST(bt->lchild, k);
    else if (k > bt->key)
        return InsertBST(bt->rchild, k);
}

//创建一棵二叉排序树是从一个空树开始,一直调用插入就好了。
BSTNode *CreateBST(KeyType A[], int n)
{
    BSTNode *bt = NULL;
    int i = 0;
    while (i < n)
    {
        InsertBST(bt, A[i]);
        i++;
    }
    return bt;
}

//销毁算法和普通的二叉树算法一样
//查找就要方便很多
BSTNode *SearchBST(BSTNode *bt, KeyType k)
{
    if (bt->key == NULL || bt->key = k)
        return bt;
    if (k < bt->key)
        return SearchBST(bt->lchild, k);
    else
        return SearchBST(bt->rchild, k);
}

//求最大\最小结点可以利用性质
KeyType maxnode(BSTNode *p)
{
    while (p->rchild != NULL)
        p = p->rchild;
    return (p->key);
}
KeyType minnode(BSTNode *p)
{
    while (p->lchild != NULL)
        p = p->lchild;
    return (p->key);
}

//一定是叶子节点


//删除要分几种情况讨论,是最麻烦的。
//是叶子就直接删除,只有左、右子树就接上去
//同时有左右子树的话可以从左中选最大的结点r代替p,然后把r删除,也可以选右中最小的,一般前者

平衡二叉树

平衡因子:左子树高度 - 右子树高度

平衡条件:平衡因子的绝对值小于1

前提是二叉排序树,

四种类型 调整前后中序序列相同是前提条件。这个建议看学校发的教材P283

删除的时候,如果不平衡了,可以通过左右孩子的平衡因子来判断做哪一种调整,P337

哈希表

又称散列表,设要存储的元素个数为n,设置一个长度为m>=n的连续内存单元,每个元素的关键字ki(i<=n-1)

为自变量,通过哈希函数映射到内存单元的地址h(ki),并存储在这个内存单元中。

哈希冲突,不同的自变量映射到同一个地址

通常情况是关键字的取值区间远大于哈希地址的变化区间

查找性能取决于:

  • 装填因子 α = n/m 应控制最终的α在0.6~0.9范围内
  • 哈希函数应该使哈希地址尽可能均匀地分布在哈希地址空间上
  • 合适的解决哈希冲突的策略。

构造哈希函数

构造哈希函数

根据关键字的结构和分布的不同可构造出许多不同的哈希函数,这里主要讨论几种常用的整数类型关键字的

  1. 直接定址法 h(k) = k + c

    当关键字分布基本连续时比较好

  2. 除留余数法 h(k) = k mod p (p <= m)

    这种该方法的关键是选好p,使得概率分布较平均,p奇数好,不大于m的素数效果最好

  3. 数字分析法

    提取关键字较均匀的数字位,适合所有关键字值都已知的情况

还有平方取中法,折叠法等。

解决冲突

开放地址法(也叫再散列法):

  1. 线性探测法

    容易产生堆积问题,d0 = h(k) di = ( (d(i-1) + 1) mod m)

  2. 平方探测法

    d0 = h(k) di = ( (d0 +- i2) mod m)

    不一定能探测到哈希表上的所有单元,但最少能探测到一半的单元

  3. 还有伪随机序列法,双哈希函数法等

再哈希法:

同时构造多个哈希函数,一个冲突就换另一个

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

//用开放地址法构造的哈希表的运算算法
#define NULLKEY -1 //空关键字值
#define DELKEY -2  //被删除关键字值
typedef int KetType;
typedef struct
{
    KetType key;
    int count; //探测次数域
} HashTable;

void InsertHT(HashTable ha[], int &n, int m, int p, KetType k)
{
    int i, adr;
    adr = k % p;                                         //adr是关键字k对应的哈希值
    if (ha[adr].key == NULLKEY || ha[adr].key == DELKEY) //可以直接放入
    {
        ha[adr].key = k;
        ha[adr].count = 1;
    }
    else
    {
        i = 1; //i 记录k发生的次数
        do
        {
            adr = (adr + 1) % m;
            i++;
        } while (ha[adr].key != NULLKEY && ha[adr].key != DELKEY);
        ha[adr].key = k;
        ha[adr].count = i; //设置探测次数
    }
    n++; //哈希表中总元素个数增1
}

void CreateHT(HashTable ha[], int &n, int m, int p, KetType keys[], int nl)
{
    //由关键字序列keys[0..nl-1]创建哈希表
    for (int i = 0; i < m; i++)
    {
        ha[i].key = NULLKEY;
        ha[i].count = 0;
    }
    n = 0;
    for (int i = 0; i < nl; i++)
        InsertHT(ha, n, m, p, keys[i]); //插入nl个
}

//删除算法
//在开放地址法处理的哈希表上不能简单的删除,因为在查找算法中空是查找失败,应该置特殊值
bool DeleteHT(HashTable ha[], int &n, int m, int p, KetType k)
{
    int adr;
    adr = k & p;
    while (ha[adr].key != NULLKEY && ha[adr].key != DELKEY)
        adr = (adr + 1) % m;
    if (ha[adr].key == k)
    {
        ha[adr].key = DELKEY;
        return true;
    }
    else
        return false;
}

void SearchHT(HashTable ha[], int m, int p, KetType k)
{
    int i = 1, adr;
    adr = k % p;
    while (ha[adr].key != NULLKEY && ha[adr].key != k)
    {
        i++;
        adr = (adr + 1) % m; //线性探测
    }
    if (ha[adr].key == k)
        printf("Success:%d compare %d times", k, i);
    else
        printf("fail");
}

ASL成功为关键字列表中每个关键字的比较次数的平均数

ASL失败为直到空时的探测次数,P357 记得要算上自己的这次

拉链法:

此时的装填因子可以设定为大于1

优点:无堆积现象,平均查找长度较短,适合无法定长的情况,元素较大时指针开销可忽略不计,删除操作容易实现

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

//拉链法
typedef int KeyType;
typedef struct node
{
    KeyType key;
    struct node *next;
} NodeType;
typedef struct
{
    NodeType *firstp;
} HashTable;

//理解这个HashTable ha数组,下标即为“地址”
void InsertHT(HashTable ha[], int &n, int p, KeyType k)
{
    int adr;
    adr = k % p;
    NodeType *q;
    q = (NodeType *)malloc(sizeof(NodeType));
    q->key = k;
    q->next = NULL;
    if (ha[adr].firstp == NULL)
        ha[adr].firstp = q;
    else
    {
        q->next = ha[adr].firstp;
        ha[adr].firstp = q;
    }
    n++;
}

void CreateHT(HashTable ha[], int &n, int m, int p, KeyType keys[], int nl)
//由关键字序列keys[0..nl-1]创建哈希表
{
    for (int i = 0; i < m; i++)
        ha[i].firstp = NULL;
    n = 0;
    for (int i = 0; i < nl; i++)
        InsertHT(ha, n, p, keys[i]);
}

//删除算法 在逻辑上更为简单,可以直接删除
bool DeleteHT(HashTable ha[], int &n, int m, int p, KeyType k) //删除哈希表中的关键字k
{
    int adr;
    adr = k % p;
    NodeType *q, *preq;
    q = ha[adr].firstp;
    if (q = NULL)
        return false;
    if (q->key == k)
    {
        ha[adr].firstp = q->next;
        free(q);
        n--;
        return true;
    }
    //首节点不为k时
    preq = q;
    q = q->next;
    while (q != NULL)
    {
        if (q->key == k)
            break;
        q = q->next;
    }
    if (q != NULL)
    {
        preq->next = q->next;
        free(q);
        n--;
        return true;
    }
    else
        return false;
}

void SearchHT(HashTable ha[], int p, KeyType k)
{
    int i = 0, adr;
    adr = k % p;
    NodeType *q = ha[adr].firstp;
    while (q != NULL)

    {
        i++;
        if (q->key == k)
            break;
        q = q->next;
    }
    if (q != NULL)
        printf("success");
    else
        printf("fail");
}

ASL成功就是比较次数求平均嘛

ASL不成功 就是每条链的结点数求平均哈哈,有几个结点就白比较了几次

9.内排序

此处的关键字是可以重复的

根据相同关键字排序后相对次序是否改变可分为稳定和不稳定,这相对于所有可能的输入实例

在排序中不需要进行数据的内、外存交换,称之为内排序。

需要关键字比较的方法有插入排序、选择排序、交换排序、归并排序

不需要的方法有基数排序

基于比较的排序主要进行两种操作:比较+移动

正序:关键字顺序正好和排序顺序相同,反序则相反。

比较排序的最好的平均理论时间复杂度为O(nlog2n) 比如堆排序、二路归并、快速排序 P367

插入排序

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

//基本数据类型
typedef int KeyType;
typedef struct
{
    int one;
    char two;
} InfoType;
typedef struct
{
    KeyType key;
    InfoType data;
} RecType;

//插入排序
//直接插入排序
void InsertSort(RecType R[], int n)
{
    int i, j;
    RecType tmp;
    for (i = 1; i < n; i++)
    {
        if (R[i].key < R[i - 1].key) //如果直接就大于有序区最大的,
        {
            tmp = R[i];
            j = n - 1;
            do
            {
                R[j + 1] = R[j];
                j--;
            } while (j >= 0 && R[j].key > tmp.key);
            R[j + 1] = tmp;
        }
    }
}

//折半插入排序
//在有序区查找位置时用折半查找就好了
void BinInsertSort(RecType R[], int n)
{
    int i, j, low, high, mid;
    RecType tmp;
    for (i = 1; i < n; i++)
    {
        if (R[i].key < R[i - 1].key)
        {
            tmp = R[i];
            low = 0;
            high = i - 1;
            while (low <= high)
            {
                mid = (low + high) / 2;
                if (tmp.key < R[mid].key)
                    high = mid - 1;
                else
                    low = mid + 1;
            }
            for (j = i - 1; j >= high + 1; j--)
                R[j + 1] = R[j];
            R[high + 1] = tmp;
        }
    }
}

//希尔排序
//这和上一章查找好像啊,分组插入,原理我感觉是避免高次运算的爆炸增长
//这里取di+1 = di/2向下取整,d1 = n/2
void ShellSort(RecType R[], int n)
{
    int i, j, d = n / 2;
    RecType tmp;
    while (d > 0)
    {
        for (i = d; i < n; i++) //这个上下界真是绝了,一步到位全部都排好,我还想了一会
        {
            tmp = R[i];
            j = i - d;
            while (j >= 0 && tmp.key < R[j].key)
            {
                R[j + d] = R[j];
                j = j - d;
            }
            R[j + d] = tmp;
        }
        d = d / 2;
    }
}

交换排序


//交换排序
//冒泡排序,从后开始,把有序的冒到最前面
void BubbleSort(RecType R[], int n)
{
    int i, j;
    RecType tmp;
    for (i = 0; i < n - 1; i++)
        for (j = n - 1; j > i; j--)
            if (R[j].key < R[j - 1].key)
            {
                tmp = R[j];
                R[j] = R[j - 1];
                R[j - 1] = tmp;
            }
}
//实际上,一旦某一趟不出现任何元素交换,就说明排好序了,可以用一个flag来达到这点

//快速排序 当年初学感觉非常巧妙的算法哈哈
//选一个枢纽(一般第一个),然后将所有的放在其前后,递归进行处理

int partition(RecType R[], int s, int t)
{ //从头尾向中扫描
    int i = s, j = t;
    RecType tmp = R[i];
    while (i < j)
    {
        while (j > i && R[j].key >= tmp.key)
            j--;
        R[i] = R[j];
        while (i > j && R[i].key <= tmp.key)
            i++;
        R[j] = R[i];
    }
    R[i] = tmp;
    return i;
}

void QuickSort(RecType R[], int s, int t) //对R[s...t]元素进行快速排序
{
    int i;
    if (s < t)
    {
        i = partition(R, s, t);
        QuickSort(R, s, i - 1);
        QuickSort(R, s + 1, t);
    }
}

选择排序

//选择排序
//基本思路是每一趟都挑出最大、最小的,适合从大量元素中选择一部分排序元素

//简单选择选择
//分成有序区和无序区,从无序区选出最的后与无序区第一个元素交换,之所以叫简单排序是因为找最小值的方法是简单的两两比较
void SelectSort(RecType R[], int n)
{
    int i, j, k;
    RecType tmp;
    for (i = 0; i < n - 1; i++)
    {
        k = i;
        for (j = i + 1; j < n; j++)
            if (R[j].key < R[k].key)
                k = j; //k记录最值的位置
        if (k != i)
        {
            tmp = R[k];
            R[k] = R[i];
        }
    }
}

//堆排序
/*看成是一颗完全二叉树的顺序存储结构,大根堆,小根堆,挑选最大元素是采用筛选方法实现的
筛选:假定某节点左右子树是大根堆,选择子节点和本身最大的上来,由于可能会破坏子树,因此递归判断*/
void sift(RecType R[], int low, int high)
{
    int i = low, j = 2 * i; //low是根节点,j指向当前结点的孩子
    RecType tmp = R[i];
    while (j <= high)
    {
        if (j < high && R[j].key < R[j + 1].key)
            j++; //看看两个孩子谁大
        if (tmp.key < R[j].key)
        {
            R[i] = R[j];
            i = j;
            j = 2 * i; //这个索引的变换有点巧妙
        }
        else
            break; //筛选结束
    }
    R[i] = tmp;
}

//构建初始堆,从最后一个分支点n/2向下取整开始,反复筛选
//for(i = n/2;i>=1;i--) sift(R,i,n)

void HeapSort(RecType R[], int n)
{
    int i;
    RecType tmp;
    for (i = n / 2; i >= 1; i--)
        sift(R, i, n); //建立初始堆
    //每次抽掉“根”,就是堆上最大那个
    for (i = n; i >= 2; i--)
    {
        tmp = R[1];
        R[1] = R[i];
        R[i] = tmp;
        sift(R, 1, i - 1);
    }
}

归并排序

多次将两个或以上的有序表合成一个新的有序表。我们研究二路归并

先分成n个长度为1的,两两归并成n/2个向上取整的有序序列,依次类推直到得到长度为n的有序序列

我不想写啦!!!!P389自己看图吧

基数排序

借助多关键字排序的思想堆单关键字进行排序。通过“分配”、“收集”

单关键字R[i].key 由d位数字组成,每一位的值都在(0 , r)之间,二进制r为2

最低位LSD和最高位优先MSD。选择方式由数据序列特点决定,越重要的位越放在后面.

想看原理在P390


//基数排序
#define MAXD 10000 //最大关键字位数
#define MAXR 10    //每一位的取值上限(开区间)
typedef struct node
{
    char data[MAXD];   //存放关键字的各位
    struct node *next; //指向下一个结点
} NodeType;

//输入数据为p为首节点的单链表
void RadixSort(NodeType *&p, int r, int d)
{
    NodeType *head[MAXR], *tail[MAXR], *t;
    int i, j, k;
    for (i = 0; i < d - 1; i++)
    {
        for (j = 0; j < r; j++)
            head[j] = head[i] = NULL; //初始化指针
        while (p != NULL)             //将原链表所有节点分配到链队
        {
            k = p->data[i] - '0'; //找到第k个链队
            if (head[j] == NULL)
            {
                head[k] = p;
                tail[k] = p;
            }
            else
            {
                tail[j]->next = p;
                tail[k] = p;
            }
            p = p->next;
        }
        p = NULL; //重新用p来收集所有节点
        for (j = 0; j < r; j++)
        {
            if (head[j] != NULL) //找到第一个非空链队,通过首位指针处理,中间已经连好了
            {
                if (p == NULL)
                {
                    p = head[j];
                    t = tail[j];
                }
                else
                {
                    t->next = head[j];
                    t = tail[k]; //如果不是第一个,就连上来
                }
            }
        }
        t->next = NULL; //别忘了!!!!!卧槽结束了!
    }
}

虽然课后习题一道没写

虽然后面跳过了一些明显不考的算法,但是终于在考试前5天写完了还是好开心

但是还有大雾和微积分:cry:

学弟学妹们,虽然这门数据结构在未来会被淘汰,但是它的绩点很重啊QWQ,不要向我一样临时抱佛脚QWQ

试验一下新功能,把我的代码看能不能直接放在这方便你们下载

数据结构

👆

待补充

串:KMP

稀疏矩阵快速转置

二叉树遍历的非递归

最短路径的两种算法

AOV和AOE

平衡二叉树的算法实现

归并排序

明年学算法设计前应该会写完这些


文章作者: Darren
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Darren !
评论
  目录