数据结构复习-线性结构(链表,栈,链栈,字符串)

数据结构复习-线性结构(链表,栈,链栈,字符串)

最近有chatgpt还是很爽的,之前想重新写一遍基本的数据结构和算法,苦于懒得自己敲模板,懒得自己编测试用例, 没想到现在有了chatgpt,各种测试用例和模板都可以自动生成了,尤其是测试, 可以让chatgot生成很严苛很变态的测试, 总能发现些问题,还是很爽, 非常的爽,于是打算重新手写一下数据结构和算法那些. 也是怕到时候笔试面试的时候太紧张了或者忘太多了写不出来, 到时候解释说我其实真的会, 我估计面试官肯定不信哈哈哈哈哈哈哈.

其实类似于这样的线性结构功能还是很强大的, 在xv6(一个教学用途的操作系统)的代码里多半是通过这种linklist管理进程,内存等等系统资源, 之前搞数学建模的时候通过for加上list能把简单重复的一些工作抽象到高一个的层次, 比如自动画图啥的, 换句话说就是我只要画一个图就能得到这种类型的所有图, 只要通过list遍历剩下的就行了, 总的来说算是性价比非常高的数据结构了, 而且对新手比较友好, 理解了LinkList,LinkStack这些能更方便理解剩下的指针型的数据结构.

LinkList

重新写一遍还是有收获的, 碰到了很多指针相关的问题, 有时候一个指针不够用还是需要两个指针跟着跑, 让一个指针先走一步,这样就很方便.

还有就是野指针的问题, free之后指向这块内存的指针实际上是被填充了别的东西, 并不是也变成了NULL,需要手动把指针设置成NULL以避免出错.

这个时候应该还有一个问题, 学了操作系统之后知道在多线程模式下这个链表其实是不安全的, 最不安全的情况莫过于一个程序用完了这个链表,并且试图释放这个链表, 其它用这个链表的进程估计就出大bug了, 所以以后用的时候尽量不那么用, 不然再维护一个相关的锁再加上啥引用计数啥的也不是那么方便.

重新写了一遍之后发现, 第一遍学的时候因为语法问题, 加上本身想的没那么透彻, 其实是很混乱的一个状态, 还是有收获的.

#define MYDEBUG
#if defined(MYDEBUG)
#include <assert.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#define DataType int
typedef struct ListNode {
    DataType val;              // 节点的值
    struct ListNode* next;  // 指向下一个节点的指针
} ListNode;

// 初始化链表,返回头节点
ListNode* createList();

// 在链表尾部插入一个节点
int insert(ListNode* head, DataType val);

// 在指定位置插入一个节点
int insertAt(ListNode* head, DataType val, int pos);

// 删除链表中第一个指定值的节点
int remove(ListNode* head, DataType val);

// 删除链表中指定位置的节点
int removeAt(ListNode* head, int pos);

// 输出链表中的所有元素
void printList(ListNode* head);

void freeList(ListNode* head);

//#define MYDEBUG
//#if defined(MYDEBUG)
//#define ASSERT(expression) assert(expression)
//#else
//#define ASSERT(expression) ((void)0)
//#endif
//
//int main() {
//    // 创建一个空链表
//    ListNode* head = createList();
//
//    // 在链表尾部插入几个节点
//    insert(head, 1);
//    insert(head, 2);
//    insert(head, 3);
//    ASSERT(head->val == 3);
//    ASSERT(head->next->val == 1);
//    ASSERT(head->next->next->val == 2);
//    ASSERT(head->next->next->next->val == 3);
    printList(head);
//    insertAt(head, 4, 2);
    printList(head);
//    ASSERT(head->val == 4);
//    ASSERT(head->next->val == 1);
//    ASSERT(head->next->next->val == 2);
//    ASSERT(head->next->next->next->val == 4);
//    ASSERT(head->next->next->next->next->val == 3);
//
//    remove(head, 2);
//    ASSERT(head->val == 3);
//    ASSERT(head->next->val == 1);
//    ASSERT(head->next->next->val == 4);
//    ASSERT(head->next->next->next->val == 3);
//
//    // 删除链表中指定位置的节点
//    removeAt(head, 1);
//    ASSERT(head->val == 2);
//    ASSERT(head->next->val == 1);
//    ASSERT(head->next->next->val == 3);
//
//    freeList(head);
//    head=NULL;
//    ASSERT(head == NULL);
//
//    return 0;
//}

// 初始化链表,返回头节点
ListNode* createList() {
    // TODO
    ListNode *head=(ListNode*)(malloc(sizeof(ListNode)));
    head->next=NULL;
    head->val=0;
    return head;
}

// 在链表尾部插入一个节点
int insert(ListNode* head, DataType val) {
    // TODO
    ListNode *node=(ListNode*)(malloc(sizeof(ListNode)));
    if(!node) return -1;
    head->val++;
    while (head->next){head=head->next;}
    node->next=NULL;
    node->val=val;
    head->next= node;

    return 0;
}

// 在指定位置插入一个节点
int insertAt(ListNode* head, DataType val, int pos) {
    ListNode *last=head;
    head->val++;
    head=head->next;
    while (pos--){
        head=head->next;
        last=last->next;
    }
    ListNode *new_node=createList();
    if(!new_node) return -1;
    last->next=new_node;
    new_node->val=val;
    new_node->next=head;
    return 1;
}
int free_node(ListNode *node){
    free(node);
    node=NULL;
    return 1;
}

// 删除链表中第一个指定值的节点
int remove(ListNode* head, DataType val) {
    head->val--;
    ListNode *last=head;
    head=head->next;
    while (head){
        if(head->val==val){
            last->next=head->next;
            free_node(head);
            return 1;
        }
        last=last->next;
        head=head->next;
    }
    return -1;
}

// 删除链表中指定位置的节点
int removeAt(ListNode* head, int pos) {
    ListNode* last=head;
    ListNode* head1=head;
    head=head->next;
    while (pos--){
        head=head->next;
        last=last->next;
        if(!head) {return -1;
            printf("有问题\n");}
    }
    head1->val--;
    last->next=head->next;
    free_node(head);
    return 1;
}

// 输出链表中的所有元素
void printList(ListNode* head) {
    if(!head) return;
    printf("size %d ;",head->val);
    head=head->next;
    while (head){
        printf("%d ",head->val);
        head=head->next;
    }
    printf("\n");
}
// 释放链表中的所有元素
void freeList(ListNode* head) {
    ListNode *last=head;
    while(last){
        head=last;
        last=last->next;
        free(head);
        head=NULL;
    }
}


ArrayStack

相较于LinkList来说, ArrayStack就相对容易些了, 只是要注意top的边界问题, 一开始想不出来为什么top的初始值是-1,

栈顶指针 top 的初始值为 -1 是因为栈顶指针的定义是指向栈顶元素上面的一个位置,而在栈为空的情况下,栈顶指针应该指向一个不存在的位置,所以将栈顶指针的初始值设为 -1,表示栈中没有元素

然后就是要稍微绕一下,因为从零开始数会少数一个,从-1开始估计是少数两个, push因为-1的原因要先加再填,pop就是先出再减了.

#include <stdio.h>
#include "stdio.h"

#define MAX_STACK_SIZE 100

typedef int ElementType;

typedef struct {
    ElementType data[MAX_STACK_SIZE];
    int top;
} Stack;

// 初始化栈
void initStack(Stack* s);

// 判断栈是否为空
int isEmpty(Stack* s);

// 判断栈是否已满
int isFull(Stack* s);

// 入栈操作
void push(Stack* s, ElementType val);

// 出栈操作
ElementType pop(Stack* s);

// 获取栈顶元素
ElementType top(Stack* s);

// 打印栈中所有元素
void printStack(Stack* s);
void testStack();
// 主函数,用于测试栈的各个功能
int main() {
    Stack s;
    initStack(&s);

    // 测试入栈操作
    push(&s, 1);
    push(&s, 2);
    push(&s, 3);

    // 测试获取栈顶元素
    printf("top: %d\n", top(&s));

    // 测试打印栈中所有元素
    printStack(&s);

    // 测试出栈操作
    ElementType val = pop(&s);
    printf("pop: %d\n", val);
    printStack(&s);

    val = pop(&s);
    printf("pop: %d\n", val);
    printStack(&s);

    val = pop(&s);
    printf("pop: %d\n", val);
    printStack(&s);

    // 测试判断栈是否为空
    if (isEmpty(&s)) {
        printf("Stack is empty\n");
    } else {
        printf("Stack is not empty\n");
    }

    // 测试判断栈是否已满
    if (isFull(&s)) {
        printf("Stack is full\n");
    } else {
        printf("Stack is not full\n");
    }
    testStack();
    return 0;
}

void initStack(Stack* s) {
    // TODO: 实现初始化栈的代码
    s->top=-1;
}

int isEmpty(Stack* s) {
    // TODO: 实现判断栈是否为空的代码
    return s->top==-1;
}

int isFull(Stack* s) {
    // TODO: 实现判断栈是否已满的代码
    return s->top>=MAX_STACK_SIZE-1;
}

void push(Stack* s, ElementType val) {
    // TODO: 实现入栈操作的代码
    if(!isFull(s))
        s->data[++s->top]=val;
}

ElementType pop(Stack* s) {
    // TODO: 实现出栈操作的代码
    if(!isEmpty(s))
        return s->data[s->top--];
    return -1;
}

ElementType top(Stack* s) {
    // TODO: 实现获取栈顶元素的代码
    if(!isEmpty(s))
        return s->data[s->top];
    return -1;
}

void printStack(Stack* s) {
    printf("stack size: %d, ",s->top+1);
    for(int i=s->top-1;i>=0;i--)
        printf("%d ",s->data[i]);
    // TODO: 实现打印栈中所有元素的代码
}

#include <assert.h>

void testStack() {
    Stack s;
    initStack(&s);

    // Test isEmpty and isFull functions
    assert(isEmpty(&s));
    assert(!isFull(&s));

    // Test push function
    for (int i = 1; i <= MAX_STACK_SIZE; i++) {
        push(&s, i);
        assert(s.top == i-1);
    }
    assert(isFull(&s));
    assert(!isEmpty(&s));

    // Test push function on full stack
    push(&s, MAX_STACK_SIZE + 1);
    assert(s.top == MAX_STACK_SIZE - 1);

    // Test pop function
    for (int i = MAX_STACK_SIZE; i >= 1; i--) {
        int val = pop(&s);
        assert(val == i);
        assert(s.top == i-2);
    }
    assert(isEmpty(&s));
    assert(!isFull(&s));

    // Test pop function on empty stack
    int val = pop(&s);
    assert(val == -1);

    // Test top function
    for (int i = 1; i <= MAX_STACK_SIZE; i++) {
        push(&s, i);
        assert(top(&s) == i);
    }
    assert(top(&s) == MAX_STACK_SIZE);

    // Test top function on empty stack
    initStack(&s);
    assert(top(&s) == -1);

    // Test printStack function
    printStack(&s);
    for (int i = 1; i <= MAX_STACK_SIZE; i++) {
        push(&s, i);
    }
    printStack(&s);
    for (int i = MAX_STACK_SIZE; i >= 1; i--) {
        pop(&s);
    }
    printStack(&s);
}

LinkStack

在链栈中,next是指向下一个节点的指针,指向栈顶元素下面的节点,也就是离栈顶最远的节点。因此,next靠近栈底部。空的链栈让top指向空即可. 专门让ChatGPT生成了很严苛的测试, 不够它生成出来的测试有个别地方是有问题的, 也不能全相信它.

这里发现了对C语言有点迷糊的地方, 使用LinkList的时候要注意一下指针的作用范围, 如果一个指针指向的不是一块单纯只放数据的内存,而是另外一个指针所在的内存,

如下:

typedef int ElementType;
typedef struct Node {
    ElementType data;
    struct Node* next;
} Node;

typedef struct {
    Node* top;
} LinkStack;
//方法1
void printStack(LinkStack* s) {
    // TODO: 实现打印链栈中所有元素的代码
    printf("link_stack: ");
    LinkStack *p=s;
    while (p->top){
        printf("%d ",p->top->data);
        p->top=p->top->next;
    }
}
//方法2
void printStack(LinkStack* s) {
    // TODO: 实现打印链栈中所有元素的代码
    printf("link_stack: ");
    Node *p=s->top;
    while (p){
        printf("%d ",p->data);
        p=p->next;
    }
}

这里方法1就是让p指向了s指向的内存, p和s都是指向相同的内存

p->top 就是 (*p).top 也就是访问了s指向的那块内存, 而s那块内存存的是指针, p->top=p->next就改变了这块内存的值, 从而改变了栈顶的位置,无意间破坏了栈的结构.

方法2没有破坏栈的结构是因为p是指针,p=p->next只是将指针向下移,

可视化一下是这样

​ s{top}----> node{data,next}------------------------------->node1{data,next}

p=s p↑ --> p->top=p->top->next -----> p->top↑

方法二大概是这样:

node1(地址99):{value:2,next=100} ;node2(地址100):{value:2,next=101}

先p->node1; 指针指向node1, p=p->next 就是把指针挪到了next里写的node2的地址上.

还好现在不是当时初学c的时候了, 当时碰到这情况真得卡好几个小时想不明白哈哈哈哈.

总的代码带测试的:

#define MAX_STACK_SIZE 4096
typedef int ElementType;
typedef struct Node {
    ElementType data;
    struct Node* next;
} Node;

typedef struct {
    Node* top;
} LinkStack;

// 初始化链栈
void initStack(LinkStack* s);

// 判断链栈是否为空
int isEmpty(LinkStack* s);

// 入栈操作
void push(LinkStack* s, ElementType val);

// 出栈操作
ElementType pop(LinkStack* s);

// 获取栈顶元素
ElementType top(LinkStack* s);

// 打印链栈中所有元素
void printStack(LinkStack* s);
#include <stdio.h>
#include <stdlib.h>
#include <cassert>

void initStack(LinkStack* s) {
    // TODO: 实现初始化链栈的代码
    s->top=NULL;
}

int isEmpty(LinkStack* s) {
    // TODO: 实现判断链栈是否为空的代码
    return s->top==NULL;
}

void push(LinkStack* s, ElementType val) {
    // TODO: 实现入栈操作的代码
    if(!s)
        return ;
    Node* node= (Node*) malloc(sizeof(Node));
    node->next=s->top;
    s->top=node;
    node->data=val;
}

ElementType pop(LinkStack* s) {
    // TODO: 实现出栈操作的代码
    if(isEmpty(s))
        return NULL;
    ElementType element=s->top->data;
    Node *f=s->top;
    s->top=s->top->next;
    free(f);
    return element;
}

ElementType top(LinkStack* s) {
    // TODO: 实现获取栈顶元素的代码
    if(isEmpty(s)) return NULL;
    return s->top->data;
}
void freeStack(LinkStack *s){
    LinkStack *p=s;
    while (p->top){
        s->top=p->top;
        p->top=p->top->next;
        free(s->top);
        free(s);
    }
}

void printStack(LinkStack* s) {
    // TODO: 实现打印链栈中所有元素的代码
    printf("link_stack: ");
    Node *p=s->top;
    while (p){
        printf("%d ",p->data);
        p=p->next;
    }
}

int main() {
    LinkStack s;
    initStack(&s);

    // 测试初始状态为空栈
    assert(isEmpty(&s));

    // 测试入栈操作
    push(&s, 1);
    push(&s, 2);
    push(&s, 3);

    // 测试获取栈顶元素
    assert(top(&s) == 3);

    // 测试打印链栈中所有元素
    printStack(&s);
    printf("\n");

    // 测试出栈操作
    ElementType val = pop(&s);
    assert(val == 3);
    printStack(&s);
    printf("\n");

    val = pop(&s);
    assert(val == 2);
    printStack(&s);
    printf("\n");

    val = pop(&s);
    assert(val == 1);
    printStack(&s);
    printf("\n");

    // 测试判断链栈是否为空
    assert(isEmpty(&s));

    // 测试入栈操作
    push(&s, 4);
    push(&s, 5);
    push(&s, 6);

    // 测试获取栈顶元素
    assert(top(&s) == 6);

    // 测试打印链栈中所有元素
    printStack(&s);
    printf("\n");

    // 测试出栈操作
    val = pop(&s);
    assert(val == 6);
    printStack(&s);
    printf("\n");

    val = pop(&s);
    assert(val == 5);
    printStack(&s);
    printf("\n");

    val = pop(&s);
    assert(val == 4);
    printStack(&s);
    printf("\n");

    // 测试判断链栈是否为空
    assert(isEmpty(&s));

    // 测试连续入栈和出栈
    push(&s, 1);
    push(&s, 2);
    push(&s, 3);


    push(&s, 4);
    push(&s, 5);
    val = pop(&s);
    assert(val == 5);
    push(&s, 6);

    // 测试获取栈顶元素
    assert(top(&s) == 6);

    // 测试打印链栈中所有元素
    printStack(&s);
    printf("\n");

    // 测试出栈操作
    val = pop(&s);
    assert(val == 6);
    printStack(&s);
    printf("\n");

    val = pop(&s);
    assert(val == 4);
    printStack(&s);
    printf("\n");


    val = pop(&s);
    printf("pop: %d\n", val);
    assert(val == 3); // 断言
    printStack(&s);


    val = pop(&s);
    printf("pop: %d\n", val);
    assert(val == 2); // 断言
    printStack(&s);

    val = pop(&s);
    printf("pop: %d\n", val);
    assert(val == 1); // 断言
    printStack(&s);

    // 测试判断栈是否为空
    if (isEmpty(&s)) {
        printf("Stack is empty\n");
        assert(s.top == NULL); // 断言
    } else {
        printf("Stack is not empty\n");
    }

    // 测试出栈空栈
    val = pop(&s);
    assert(val == NULL); // 断言

    // 测试获取空栈的栈顶元素
    val = top(&s);
    assert(val == NULL); // 断言

    // 测试判断链栈是否为空
    if (isEmpty(&s)) {
        printf("LinkStack is empty\n");
        assert(s.top == NULL); // 断言
    } else {
        printf("LinkStack is not empty\n");
    }

    // 测试链栈出栈空栈
    val = pop(&s);
    assert(val == NULL); // 断言

    // 测试链栈获取空栈的栈顶元素
    val = top(&s);
    assert(val == NULL); // 断言

    // 测试链栈入栈空指针
    push(NULL, 1);
    assert(s.top == NULL); // 断言

    // 释放链栈
    freeStack(&s);

    return 0;
}

String

C程序设计语言(黑皮书的那个)里有很多关于字符串的示例和练习, 还记得当时是做了几十到题之后熟悉了c语言的指针, 梦回当初了属于是, 时间长了手法有点生疏了, 还是要开调试. 测试用例也是ChatGPT生成的.

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

static int strlen(char *s){
    char *a=s;
    while (*(++s));
    return s-a;
}

char* createString(int len);
char* copyString(char* s);
char* concatenateStrings(char* s1, char* s2);
int compareStrings(char* s1, char* s2);
char* findSubstring(char* s, char* sub);
int getLength(char* s);
void reverseString(char* s);

char* createString(int len){
    char *s= (char *)malloc(sizeof(char )*(len+1));
    /*为了测试顺便随便塞点东西进去*/
    memset((void *)s,'0',sizeof(char )*len);
    memset((void *)(s+len),0,sizeof(char ));
    return s;
}
char* copyString(char* s){
    char *first=s;
    char *r,*p;
    while (*s++)
        ;
    /* *s=\0 */
    r= (char*)malloc(sizeof(char )*(s-first+1));
    p=r;
    while (first!=s)
        *(p++)=*(first++);
    *p=0;
    return r;
}
char* concatenateStrings(char* s1, char* s2){
    int len= strlen(s1)+ strlen(s2);
    char *r,*p;
    r= (char *)malloc(sizeof(char )*(len+1));
    p=r;
    while (*s1++)
        *p++=*s1;
    while (*s2++)
        *p++=*s2;
    return r;
}
int compareStrings(char* s1, char* s2){
    while ((*s1++==*s2++)&&(*s1)&&(*s2));
    if((*s1==*s2)&&(*s1==0)) return 1;
    return 0;
}

char* findSubstring(char* s, char* sub){
    int len_sub= strlen(sub);
    int len_s= strlen(s);
    char *r=(char *)calloc(len_sub+1, sizeof(char ));
    char *p=s;
    while (p+len_sub<s+len_s){
        memcpy(r,p,len_sub*(sizeof(char )));
        if(compareStrings(r,sub))
            return r;
        p++;
    }
    return NULL;
}
int getLength(char* s){
    char *a=s;
    while (*(++s));
    return s-a;
}
void reverseString(char* s){
    char *s1=s+ strlen(s)-1;
    while (s+1<s1){
        char temp=*s1;
        *s1=*s;
        *s=temp;
        s++,s1--;
    }
}

int main() {
    // 测试创建字符串函数
    char *s1 = createString(10);
    printf("createString: %s\n", s1);
    free(s1);

    // 测试复制字符串函数
    char *s2 = "hello";
    printf("strlen: %d\n", strlen(s2));
    char *copy = copyString(s2);
    printf("copyString: %s\n", copy);
    free(copy);

//     测试拼接字符串函数
    char *s3 = "world";
    char *result = concatenateStrings(s2, s3);
    printf("concatenateStrings: %s\n", result);
    free(result);

    // 测试比较字符串函数
    char *s4 = "hello";
    char *s5 = "world";
    int cmp1 = compareStrings(s2, s4);
    int cmp2 = compareStrings(s2, s5);
    printf("compareStrings: %d %d\n", cmp1, cmp2);

    // 测试查找子字符串函数
    char *s6 = "hello world";
    char *sub1 = "wor";
    char *sub2 = "abc";
    char *find1 = findSubstring(s6, sub1);
    char *find2 = findSubstring(s6, sub2);
    printf("findSubstring: %s %s\n", find1, find2);
//
    // 测试获取字符串长度函数
    int len1 = getLength(s2);
    int len2 = getLength("");
    printf("getLength: %d %d\n", len1, len2);
//
    // 测试反转字符串函数
    char s7[] = "abcdef";
    reverseString(s7);
    printf("reverseString: %s\n", s7);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值