【数据结构】 上机实操心得

#include <stdio.h>//链表...
#include <stdlib.h>
typedef struct ln{
    int value;
    struct ln *next;

} LinkNode,*LinkList;

void printList(LinkList l1);
LinkList createLinkList();
LinkNode* createLinkNode(int v);
void addrear(LinkList head, int value);
void addhead(LinkList head, int value);
void insert_rear(LinkList head , LinkNode *s , int index);
void insert_front(LinkList head , LinkNode *s , int index);
int main()
{
    LinkList l1 = createLinkList();
    LinkNode *a,*b;
    a = createLinkNode(0);
    b = createLinkNode(4);
    addrear(l1,3);
    addhead(l1,2);
    addhead(l1,1);
    printList(l1);
    insert_rear(l1,a,2);//index 为2
    insert_front(l1,b,0);// index 0
    printList(l1);
    return 0;
}
void printList(LinkList l1)
{
    if(l1->next == NULL){
        printf("empty\n");
    }
    else{
        LinkNode *p = l1;
        while(p->next != NULL){
            p = p->next;
            printf("*%d",p->value);
        }
        printf("\n");
    }
}

LinkList createLinkList()
{
    LinkList head = (LinkList)malloc(sizeof(LinkNode));
    head->next = NULL;
    head->value = 0;
    return head;
}

LinkNode* createLinkNode(int v)
{
    LinkNode *p = (LinkNode*)malloc(sizeof(LinkNode));
    p->next = NULL;
    p->value = v;
    return p;
}
void addrear(LinkList head, int value)
{
    LinkNode *s = createLinkNode(value);
    LinkNode *p = head;
    while(p->next != NULL){
        p = p->next;
    }
    s->next = p->next;
    p->next = s;

}
void addhead(LinkList head, int value)
{
    LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode));
    s->next = head->next;
    s->value = value;
    head->next = s;
}
void insert_rear(LinkList head , LinkNode *s , int index)
{
    if(head->next == NULL){
        head->next = s;
        s->next = NULL;
    }
    else{
        LinkNode *p = head;
        while(p->next != NULL && (index--)>-1){
            p = p->next;
        }
        if(index != -1){
            printf("index erro\n");
        }
        else{
            s->next = p->next;
            p->next = s;
            printf("insert rear success\n");
        }
    }
}

void insert_front(LinkList head , LinkNode *s , int index)
{
    if(head->next == NULL){
        head->next = s;
        s->next = NULL;
    }
    else{
        LinkNode *p = head;
        LinkNode *q = (LinkNode*)malloc(sizeof(LinkNode));
        q->next = p;
        while(p->next != NULL && index>-1){
            p = p->next;
            q = q->next;
            index--;
        }
        if(index != -1){
            printf("index erro\n");
        }
        else{
            s->next = q->next;
            q->next = s;
            printf("insert front success\n");
        }
    }
}
  1. 以上为使用头结点的版本。
  2. 为什么要使用头结点呢?课本上描述的是,为了解决"第一个结点"再很多操作中的问题。例如,若不使用头结点,想要在第一个位置以及其他位置,插入结点的代码是不同的。
    head = p;//头插
    
    p->next = q->next;//q结点后面插
    q->next = p;
    归根结底还是因为头“指针”不像结点那样有next等属性
  3. 若使用头结点,记得队列为空的条件是head->next = NULL
  4. 结构体声明的格式要注意,后面起两个别名typedef了两个,一个节点类型ListNode,一个指针类型*LinkList
  5. 上面对于LinkList起名用了 head和l1两个,感觉以后都叫l1吧;
  6. c里面没有null,要写NULL
  7. LinkNode *q = (LinkNode*)malloc(sizeof(LinkNode));这里如果写sizeof(*LinkList)会报错,不知何因
  8. 删除节点记得free()


关于c自定义库函数以及头文件与其源代码之间的关系
  1. c头文件何其同名源文件间关系
    https://blog.csdn.net/lee244868149/article/details/39341751
  2. c头文件及实现
    https://jingyan.baidu.com/article/6b97984dcfd2991ca2b0bf03.html
循环链表(单)

  1. 对于单循环链表,只需将表尾的节点指向head即可
  2. 单循环链表可以从任何一个节点开始进行遍历,而单链表只能从头节点开始
  3. 有时对链表的操作是在表尾、表头,此时可以改变链表的标识方法(提高效率),不用头指针而用一个指向尾节点的指针R,则表头表尾都可以用R表示。
  4. 对于单循环链表,初始时head->next = head,后续添加时保证最后一个节点始终指向头结点;对双向循环链表,起始时head->next = head; head->pre = head
双向链表
  1. 结构体中增加一个指针域pre(prior)。
  2. 对于一般的单链表,找前驱是O(n),后继是O(1);而对于双向链表,找前驱与后继都是O(1)
  3. 相对应的,双向链表可以选择是否带头结点以及是否为循环形式
链表的其他算法
  1. 对于单链表以及双向链表的逆置reverse
    void _reverse(LinkList l1)//很经典的算法
    {
        LinkNode *p = l1;
        if(p->next == NULL){
    
        }
        else{
            printf("revers ! here we go!!!\n");
            LinkNode *q;
            p = p->next;
            l1->next = NULL;
            while(p){
                q = p;
                p = p->next;
                q->next = l1->next;
                l1->next = q;
            }
        }
    }

  2. 对于单链表的去重 。最简单的二重循环注意几点① 因为涉及到删除这个节点,所以q->next->value
    if(p->value == q->next->value)
    ②因为要free,需保留p->next
    r = q->next;
    q->next = q->next->next;
    free(r);
    想过为啥while的判定是p->next 而不是 p ,可能p->next更精简吧,到最后一个节点就会停下
    void qvchong(LinkList l )
    {
        LinkNode *p = l->next;
        LinkNode *r,*q;
        while(p->next){
            q = p;
            while(q->next){
                if(p->value == q->next->value){
                    r = q->next;
                    q->next = q->next->next;
                    free(r);
                }
                else{
                    q=q->next;
                }
            }
            p = p->next;
        }
    }
    

  3. 对于单调递增序排列的两单链表a,b进行归并,成一个按元素值递减的有序链表c,要求直接把a,b上的节点挂到c上。注意点是:     因为涉及到摘除这个节点 q->next->value
    if(p->next->value <= q->next->value)
    LinkList hebing(LinkList l, LinkList s)
    {
        LinkNode *p = l;
        LinkNode *q = s;
        LinkNode *r;
        LinkList c = (LinkList)malloc(sizeof(LinkNode));
        if(c == NULL)
            printf("c***null\n");
        c->next = NULL;
    
        while(p->next !=NULL && q->next!= NULL){
            if(p->next->value <= q->next->value){
                r = p->next;
                p->next = p->next->next;
                r->next = c->next;
                c->next = r;
    
            }else{
                r = q->next;
                q->next = q->next->next;
                r->next = c->next;
                c->next = r;
            }
        }
        if(p->next!=NULL){
            while(p->next){
                r = p->next;
                p->next = r->next;
                r->next = c->next;
                c->next = r;
            }
        }else{
            while(q->next){
                r = q->next;
                q->next = q->next->next;
                r->next = c->next;
                c->next = r;
            }
        }
        free(p);
        free(q);
        return c;
    }
  4. 单链表转为双向循环链表。先生成一个双向循环链表头结点,在一个一个元素加进去即可。注意头结点pre和next初始化为都指向自身

有序表

#include <stdio.h>
#include <stdlib.h>
#define MAX 20//MAX减一为数组最大的index值
typedef struct{
        int data[MAX];
        int last;//last指向最后一个元素的ndex
    }SeqList;
 void init_erro(SeqList *s);
 SeqList* init();
int insert(SeqList *s , int i , int value);
 int delet(SeqList *s ,int i);
 int searchByValue(SeqList *s , int value);
int main()
{
    int i ;
    SeqList *s;
    s = init();
    for( i = 0 ; i<16 ; i++){
        insert(s,i,i);
    }

    for(i = 0 ; i<=s->last ; i++){
        printf("*%d",s->data[i]);
    }
    printf("\n");

    delet(s , 0);//删除完零号index以后原来index为6的跑到了index为5
    delet(s , 6);

    for(i = 0 ; i<=s->last ; i++){
        printf("*%d",s->data[i]);
    }
    printf("\n");
    return 0;
}

 SeqList* init()
 {
     SeqList *s;
     s = (SeqList *)malloc(sizeof(SeqList));
     s->last = -1;
     return s;
 }

void init_erro(SeqList *s)//会显示段错误 segment fault
{
    s = (SeqList *)malloc(sizeof(SeqList));
    s->last = -1;
}

 int insert(SeqList *s , int i , int value)
 {
     int j = 0,index = s->last;
     if(index < (MAX-1) && i >= 0 && i<=index+1){
        for(j = index ; j>=i ; j--){
            s->data[j+1] = s->data[j];
        }
        s->data[i] = value;
        s->last++;
        return 1;
     }else
     {
        printf("insert unseccess");
        return 0;
     }

 }

 int delet(SeqList *s ,int i)
 {
     int index = s->last;
     if( i>= 0 && i <= index){
        int j = 0;
        for( j = i ; j < index ; j++){
            s->data[j] = s->data[j+1];
        }
        s->last--;
        return 1;
     }else{
        printf("delete unseccess");
        return 0;
     }
 }

int searchByValue(SeqList *s , int value)//返回索引值,若未找到返回-1
{
    int i = 0;
    for( i ; i <= s->last ; i++ ){
        if(s->data[i] == value){
            return i;
        }
    }
    return -1;
}

  1. 生成一个有序表,只能用return的方式。
    SeqList* init()
     {
         SeqList *s;
         s = (SeqList *)malloc(sizeof(SeqList));
         s->last = -1;
         return s;
     }
    不可用
    void init_erro(SeqList *s)//会显示段错误 segment fault
    {
        s = (SeqList *)malloc(sizeof(SeqList));
        s->last = -1;
    }
    可能因为传进去的头结点还未初始化吧,要注意(原因见下面大红字)

---------------------------------------------------------

堆栈

  1. 顺序栈
  2. 链栈

堆栈是运算受限的线性表(只能对顶端进行操作),因此线性结构(顺序表,单链表)对栈也是适用的,只不过操作不同。

顺序栈

  1. 顺序栈的结构与顺序表基本一致
    typedef struct ss{
        int data[MAX];
        int top;
    }seqStack;


链栈

  1. 结构体
    typedef struct ll{
        int value;
        struct ll* next;
    }stackNode,*linkStack;


  2. 链栈和单链表就有一定的区别了;单链表一般都会有一个头节点,而链栈是不需要有头节点的,只需要申请一个单纯的指针(不用为其malloc分配节点的内存)指向其栈顶的节点。
  3. 根据上条所说,对于链栈的初始化,只用 即可,不用项单链表一样有专门初始化的函数
    linkStack top = NULL;
    对于链栈的其他操作也都与单链表有着细节上的区别,一定要注意(后面有详细代码)

  4. (***)往函数传指针,并改变其指向是不可行的。对于编译器来说,指针的地址也会和普通变量一样考备一份(这也说明了为什么在有序表那里不能通过init(seqlist *s)来初始化 ,只能通过return方式 ) https://blog.csdn.net/bin510729392/article/details/51476327
    void push_linkstack(linkStack l , int value)//错误方式
    {
        stackNode *p = (stackNode*)malloc(sizeof(stackNode));
        if(p == NULL){
            return 0;
        }
        else{
            p->value = value;
            p->next = l;//首次写错为了p->next = l->next
            l = p;首次写错为了l->next = p
            return 1;
        }
    
    }
    只能通过return得到push后的指向栈顶的指针
    linkStack push_linkstack(linkStack l , int value)//错误方式
    {
        stackNode *p = (stackNode*)malloc(sizeof(stackNode));
        if(p == NULL){
            return NULL;
        }
        else{
            p->value = value;
            p->next = l;//首次写错为了p->next = l->next
            l = p;首次写错为了l->next = p
            return l;
        }
    
    }


  5.  链栈的操作代码 ①对于pop以及push因为top指针的指向变化了,所以必须return出新的top指针(第四条)②其他细节,不要把指向栈顶的的指针的一些操作和单链表中对头节点的操作弄混
    int isEmpty_linkstack(linkStack l)
    {
        if(l == NULL)//首次写错为了l->next==NULL
            return 1;
        else
            return 0;
    }
    
    
    linkStack pop_link(linkStack l , int *a)
    {
        if(isEmpty_linkstack(l)){
            return NULL;
        }
        else{
            stackNode *p = l;
            *a = l->value;
            l = l->next;
            free(p);
            return l;
        }
    }
    linkStack push_linkstack(linkStack l , int value)//思想纠正一下;在函数里改变指针指向也是没用的
    
    {
     stackNode *p = (stackNode*)malloc(sizeof(stackNode));
    
    if(p == NULL){ return NULL; } else{ p->value = value; p->next = l;//首次写错为了p->next = l->next l = p;首次写错为了l->next = p return l; }}void getTop_linkstack(linkStack l , int *a)//思想纠正一下;在函数里改变指针指向也是没用的{ if(isEmpty_linkstack(l)){ printf("linkstack is empty\n"); } else{ *a = l->value; printf("top element %d\n",l->value); }}void print_linkstack(linkStack l)//其实对于堆栈操作感觉不应该有遍历{ linkStack p = l; while(p != NULL){ printf("*%d",p->value); p = p->next; }}
    main    注意初始化空指针
    int main()
    {
        linkStack top = NULL;
        int *a = (int*)malloc(sizeof(int));//不要写成 int* a;千万记得不要接引用空指针
        top = push_linkstack(top,1);
        top = push_linkstack(top,2);
        getTop_linkstack(top,a);
        top = pop_link(top,a);
        getTop_linkstack(top,a);
    
        return 0;
    }

队列

  1. 有序表循环队列
  2. 链表队列
有序表循环队列

    有序表队列我写了两个版本,其一为结构体中不含num的以及含num的。以后不做特殊要求就用含num的。

以下为不含num的版本 (可存MAX-1个数)

typedef struct seqqueue{
    int date[MAX];
    int head,rear;
}seqQueue;
/*
   * *******结构体中不含有个数信息的循环顺序队列******************************************************
    1.队满和队空的判断条件不能相同。队空为p->rear == p->head;队满为(p->rear+1)%MAX == p->head
    2.所以对应的,队列中最多存MAX-1个数
    3.p->head = p->next = MAX-1;初始化必须这样写,不能写-1
    4.入队出队对指针的操作 p->rear = (p->rear+1)%MAX; p->head = (p->head+1)%MAX
   * **********************************************************************************************
 */


seqQueue* initSeqQueue()
{
    seqQueue *p = (seqQueue*)malloc(sizeof(seqQueue));
    /*
        p->head = p->next = MAX-1;
        ***********只能这么写,不能初始化为-1*********************
        因为如果head一直为-1,那队列就算满了,条件(p->rear+1)%MAX == p->head照样不会执行
    */
    p->head = 3;
    p->rear = 3;
    return p;
}

void in_seqQueue(seqQueue *p,int value)
{
    if((p->rear+1)%MAX == p->head){
        printf("seqQueue is full \n");

    }
    else{
        p->rear = (p->rear+1)%MAX;
        p->date[p->rear] = value;
        printf("current queue`s rear element is %d\n",p->date[p->rear]);
    }

}

void out_seqQueue(seqQueue *p)
{
    if(p->head == p->rear)
        printf("seqQueue is empty \n");
    else{
        p->head = (p->head+1)%MAX;

        printf("current queue`s head element %d is out\n",p->date[p->head]);
    }

}

void getSeqQueueSize(seqQueue *p)
{
    if(p->rear >= p->head){
        printf("size is %d\n",p->rear = p->head);
    }else{
        if(p->rear == p->head -1)
            printf("size is %d,full\n",MAX-1);
        else
            printf("size is %d,full\n",MAX-p->head+p->rear);

    }
}
含num的版本(可存MAX个数)
typedef struct seqqueue11{
    int date[MAX];
    int head,rear;
    int num;
}seqQueue1;
/*

    *******此为结构体中包含个数信息的循环队列,******************************************************
    1.判空判满更加方便,因为只需要对num判断即可
    2.可以存入MAX个数
    3.p->head = p->next = MAX-1;初始化就统一这样写,不能写-1
    4.入队出队对指针的操作p->rear = (p->rear+1)%MAX; p->head = (p->head+1)%MAX
   *****************************************************************************************
 */


seqQueue1* initSeqQueue1()
{
    seqQueue1 *p = (seqQueue1*)malloc(sizeof(seqQueue1));
    p->head = p->rear = -1;
    p->num = 0;
    return p;
}

void in_seqQueue1(seqQueue1 *p,int value)
{
    if(p->num == MAX)//
        printf("full\n");
    else{
        p->rear = (p->rear+1)%MAX;
        p->date[p->rear] = value;
        p->num++;
        printf("in %d\n",p->date[p->rear]);
    }
}

void out_seqQueue1(seqQueue1 *p,int *value)
{
    if(p->num == 0)
        printf("empty\n");
    else{
        p->head = (p->head+1)%MAX;
        *value = p->date[p->head];
        printf("out %d\n",p->date[p->head]);
        p->num--;
    }
}


链表队列 需要头节点

注意结构体的声明

typedef struct listnode
{
    int value;
    struct listnode *next;
}lNode;

typedef struct lqueue
{
    lNode *head,*rear;
    int num;
}linkQueue;
/**********************************************************
    1.涉及到了两重嵌套结构体的初始化问题
    2.出队时如果队内只有一个元素,操作有所不同。(要置rear为head)

**************************************************************
*/
linkQueue* initLinkQueue()
{
    linkQueue *p = (linkQueue*)malloc(sizeof(linkQueue));
    lNode *headnode = (lNode*)malloc(sizeof(lNode));
    headnode->next = NULL;
    p->head = headnode;
    p->rear = headnode;
    p->num = 0;
    return p;
}

void in_linkQueue(linkQueue*p,int value)
{
    lNode *q = (lNode*)malloc(sizeof(lNode));
    if(q == NULL){
        printf("full\n");
    }else{
        q->value = value;
        q->next = p->rear->next;
        p->rear->next = q;
        p->rear = q;
        p->num++;
        printf("in %d\n",p->rear->value);
    }

}

void out_linkQueue(linkQueue *p)
{
    if(p->num == 0){
        printf("empty\n");
    }else{
        if(p->num == 1){
            lNode *q = (lNode*)malloc(sizeof(lNode));
            q = p->head->next;
            p->head->next = q->next;
            p->rear = p->head;
        }else{
            lNode *q = (lNode*)malloc(sizeof(lNode));
            q = p->head->next;
            p->head->next = q->next;

        }

        p->num--;
        printf("out %d",q->value);
        free(q);
    }
}

6.17实操 KMP算法 收获颇丰 O(m+n)

相关知识补充

  1. c语言中strlen()的用法。“如果字符的个数等于字符数组的大小,那么strlen()的返回值就无法确定了“, 解决方法:申请数组时,留一些多与空间
    char pattern[10] = "abcaababc";

    https://www.cnblogs.com/xiaodingmu/p/6279723.html
  2. c语言初始化数组的三种方法。{0}、memset()、for循环
    ///void *memset(void *s, v, unsigned n);v是要初始化的值
    int next[9] ;
    memset(next,-2,sizeof(int)*9);
  3. 教训:这次在getNext 中出现了一个问题。按答主给出的代码(getNext部分)对next数组操作的时候是会越界的,但是编译器没有给出任何错误提示,看了memory dump 跟踪了一下pattern才发现pattern一部分字符被next越界的给修改了。今后一定要注意
    ///错误版本
    void getNext(char* p,int *next,int arraycount)
    {
        *next = -1;
        int i = 0 , j = -1;
    
        while( i < arraycount){///正确为arraycount-1
    
            if(j == -1 || *(p+i) == *(p+j)){
                j++;
                i++;
                next[i]= j;///这里一开始发生了越界,执行过程中没有给出错误提示,最后看了memory dump才发现问题
            }
            else{
                j = *(next+j);
            }
        }
    }
  4. 通过上条可知,通过指针或数组名在函数中均可行。

算法思路

    整体思路:再BF的基础上改进,在待匹配字符串上不再进行回溯,而是当遇到不匹配字符时修改patern的指针。

    重点:对于PMT数组的理解,next数组是根据PMT得到的。

如何更好的理解和掌握 KMP 算法? - 海纳的回答 - 知乎 https://www.zhihu.com/question/21923021/answer/281346746

    细节:PMT中的值是字符串的前缀集合与后缀集合的交集中最长元素的长度。详见zhihu

  1. 整体分为两块:先得到next数组,再依靠next数组更新pattern的指针。
  2. next数组得到的方式与KMP算法类似(メ`ロ´)/

完整代码

void getNext(char* p,int *next,int arraycount)
{
    *next = -1;
    int i = 0 , j = -1;

    while( i < arraycount-1){

        if(j == -1 || *(p+i) == *(p+j)){
            j++;
            i++;
            *(next+i) = j;
        }
        else{
            j = *(next+j);
        }
    }
}

void KMP(char *_string , char *pattern , int *next,int stringcount , int patterncount)
{
    int i = 0 , j = 0;

    while(i < stringcount && j< patterncount){
        if(j == -1 || *(_string+i) == *(pattern+j)){
            i++;
            j++;
        }
        else{
            j = *(next+j);
        }
    }

    if(j == patterncount){
        printf("match success init index is %d",i-patterncount);
    }
    else{
        printf("match fail");
    }

}

main

    char a[18] = "aabcbabcabcaababca";
    /*******************************************************************
    如果字符的个数等于字符数组的大小无法使用strlen获取长度,因为找不到\0
    所以这里pattern大小多了一个
    */
    char pattern[10] = "abcaababc";

    int next[9] ;
    memset(next,-2,sizeof(int)*9);
    getNext(pattern,next,strlen(pattern));
    KMP(a,pattern,next,18,9);

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值