数据结构应用题第二章线性表代码

头文件是为了后面初始化和打印链表的时候不用重复定义,实现在t1.c和t2.c两个文件中

#ifndef T2_H
#define T2_H

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

// 定义一个链表结点的结构体
typedef int ElemType;
typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;

// 初始化带头结点的指针
bool InitList_withHead(LinkList *L);
// 初始化不带头结点的指针
bool InitList_withoutHead(LinkList *L);
// 打印一个带头结点的指针
void printfLinkList2(LinkList L);
// 打印一个不带头结点的链表
void printfLinkList(LinkList L);

#endif
  • 在带头结点的单链表L中删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一
#include "t2.h"

// init
bool InitList_withHead(LinkList *L){
    *L=(LNode *)malloc(sizeof(LNode));
    if(*L==NULL)
        return false;
    (*L)->next=NULL;
    return true;
}
void delete_x_node2(LinkList *L,ElemType x){
    LNode *p=(*L)->next;//指向下一个结点
    LNode *q=*L;//指向前一个结点
    if(p==NULL)
        return;
    while(p!=NULL){
        if(p->data==x){
            q->next=p->next;
            free(p);
            p=q->next;
        }else{
            q=q->next;
            p=p->next;
        }
    }
}
void printfLinkList2(LinkList L){
    if(L->next==NULL)
        return;

    LNode *p=L->next;
    while(p!=NULL){
        printf("%d->",p->data);
        p=p->next;
    }
    printf("\n");
}
void test2(){
    LinkList L;
    InitList_withHead(&L);
    LNode *p=L;
    for(int i=0;i<4;i++){
        LNode *s=(LNode *)malloc(sizeof(LNode));
        s->data=i+1;
        s->next=NULL;
        p->next=s;
        p=p->next;
    }
    printfLinkList2(L);
    delete_x_node2(&L,1);
    printfLinkList2(L);
}

// void main(){
//     test2();
// }
  • 设计一个递归算法,删除不带头结点的单链表L所有值为x的结点
#include"t2.h"

// init
bool InitList_withoutHead(LinkList *L){
    (*L)==NULL;
    return true;
}

void delete_x_node(LinkList *L,ElemType x){
    if(*L==NULL)//如果当前是一个空表,直接返回true;
        return;

    if((*L)->data==x){//如果当前节点的值是x,进行删除
        LNode *p=*L;
        (*L)=p->next;
        free(p);
        delete_x_node(L,x);//递归调用
    }
    delete_x_node(&((*L)->next),x);//递归调用
    
}
void printfLinkList(LinkList L){
    LNode *p=L;
    while(p!=NULL){
        printf("%d->",p->data);
        p=p->next;
    }
    printf("\n");
}
void test1(){
    LinkList L;
    InitList_withoutHead(&L);
    LNode *p=L;
    for(int i=0;i<4;i++){
        LNode *s=(LNode *)malloc(sizeof(LNode));
        s->data=i+1;
        s->next=NULL;
        if(i==0){//第一个结点的插入,不带头结点的链表需要特殊处理
            L=s;
            p=L;
        }
        else{
            p->next=s;
            p=p->next;
        }

    }
    printfLinkList(L);
    delete_x_node(&L,1);
    printfLinkList(L);
}
// void main(){
//     test1();
// }
  • 设L为带头结点的单链表,编写算法实现从尾到头反向输出每一个结点
#include "t2.h"
// 实际使用中,我们可以直接先编写一个函数,去掉头指针结点,再进行递归
void Invert_printf(LinkList L,LNode *p){
    if(p==L&&p->next==NULL)//表示空链表
        return;
    if(p->next==NULL){//链表最后一个结点
        printf("%d->",p->data);
        return;
    }

    Invert_printf(L,p->next);
    if(p!=L)//排除掉带头结点干扰
        printf("%d->",p->data);
    return;
}
// void main(){
//     LinkList L;
//     InitList_withHead(&L);
//     LNode *p=L;
//     for(int i=0;i<4;i++){
//         LNode *s=(LNode *)malloc(sizeof(LNode));
//         s->data=i+1;
//         s->next=NULL;
//         p->next=s;
//         p=p->next;
//     }
//     p=L;
//     printfLinkList2(L);
//     Invert_printf(L,p);
// }
  • 在带头结点的单链表L中删除一个最小值结点的高效算法,假设最小值结点是唯一的
#include "t2.h"

bool delete_min(LinkList *L,ElemType x){
    if((*L)->next==NULL)//空表
        return false;

    LNode *q=*L;//指向最小节点的前一个结点
    LNode *p=*L;//动态寻找最小值结点
    ElemType min=p->next->data;

    while(p->next!=NULL){
        if(p->next->data<min){
            q=p;
        }
        p=p->next;
    }
    p=q->next;
    q->next=p->next;
    free(p);
    return true;
}
  • 将带头结点的单链表就地逆置,所谓“就地”是指辅助空间复杂度为O(1)
#include "t2.h"

// 遍历找到尾结点,然后从头结点的位置开始逐个删除头结点,把删除的结点逐个头插法到尾结点
bool InvertLinkList(LinkList *L){
    if((*L)->next==NULL)
        return true;

    LNode *s=*L;//表示要删除的结点
    LNode *q=*L;//尾结点
    while(q->next!=NULL){
        q=q->next;
    }
    // 注意倒置后q结点就是第一个节点,所以循环结束的条件就是!=q
    while((*L)->next!=q){
        // 删除头结点
        s=(*L)->next;
        (*L)->next=s->next;
        // 头插法插入结点
        s->next=q->next;
        q->next=s;
    }
    return true;

}

// 尾插法建立单链表
void List_TailInsert(LinkList *L){
    int e;
    LNode *s,*r=*L;
    scanf("%d",&e);
    while(e!=999){
        s=(LNode *)malloc(sizeof(LNode));
        s->data=e;
        r->next=s;
        r=s;
        scanf("%d",&e);
    }
    // 如果这里不添加NULL,则r的后面就会随意指向一个地址,将出现大问题
    r->next=NULL;
}
void test5(){
    LinkList L;
    InitList_withHead(&L);
    List_TailInsert(&L);
    printfLinkList2(L);
    InvertLinkList(&L);
    printfLinkList2(L);
}
// void main(){
//     test5();
// }

解法2思路:解法1容易出现循环结束条件的错误,解法2就是将头结点单独摘下,其他结点作为一个单独链表,因为这个链表空间本身存在的,所以空间复杂度还是O(1),然后从第一个节点开始逐个删除不带头结点的单链表(拆下的新链表)的头结点,头插法插入到L中。
解法3思路:直接法next指针域修改为前一个指针,简单明了,只是需要多来几个节点指针LNode而已。

  • 有一个带头结点的单链表L,设计一个算法使其元素插入时递增有序
#include "t2.h"
// 循环找到当前链表中比x大的前向节点,执行在此前向节点后插入结点操作。这种算法的时间复杂度为
void incleaseOrder(LinkList *L,ElemType x){
    LNode *p=*L;//指向当前要插入的结点的前向结点

    while(p->next!=NULL){
        if(p->next->data>x)//如果p的后一个结点的数据大于x,直接结束循环
            break;
        p=p->next;
    }
    LNode *s=(LNode *)malloc(sizeof(LNode));
    s->data=x;
    s->next=p->next;
    p->next=s;
}
void insert(LinkList *L){
    ElemType x=0;
    scanf("%d",&x);
    while(x!=999){
        incleaseOrder(L,x);
        scanf("%d",&x);
    }

}
void test6(){
    LinkList L;
    InitList_withHead(&L);
    insert(&L);
    printfLinkList2(L);
}
void main(){
    test6();
}
  • 有一个带头结点的单链表L,设计一个算法使其元素递增有序(人话就是给单链表递增排序,一开始理解错写成上面的代码了)
    时间复杂度为O(n*n)
#include "t2.h"
// 将当前链表切分成两个,头结点指向的链表动态维护一个递增有序的链表,而另一个切分的链表用来逐个增加头结点指向的链表
void sort(LinkList *L){
    LNode *p,*s;//p用来获取一个需要增加的节点,s是为了防止第二个链表断链
    if((*L)->next==NULL)//空链表
        return;

    s=(*L)->next;
    (*L)->next=NULL;
    while(s!=NULL){
        p=s;
        s=s->next;
        incleaseOrder(L,p);//在L中按序插入结点p
    }
}
void incleaseOrder(LinkList *L,LNode *s){
    LNode *p=*L;//指向当前要插入的结点的前向结点

    while(p->next!=NULL){
        if(p->next->data>s->data)//如果p的后一个结点的数据大于x,直接结束循环
            break;
        p=p->next;
    }
    s->next=p->next;
    p->next=s;
}
// 尾插法建立单链表
// void List_TailInsert(LinkList *L){
//     int e;
//     LNode *s,*r=*L;
//     scanf("%d",&e);
//     while(e!=999){
//         s=(LNode *)malloc(sizeof(LNode));
//         s->data=e;
//         r->next=s;
//         r=s;
//         scanf("%d",&e);
//     }
//     // 如果这里不添加NULL,则r的后面就会随意指向一个地址,将出现大问题
//     r->next=NULL;
// }
void test6(){
    LinkList L;
    InitList_withHead(&L);
    List_TailInsert(&L);
    printfLinkList2(L);
    sort(&L);
    printfLinkList2(L);
}
void main(){
    test6();
}
  • 设一个带头结点的单链表中所有元素结点数据值都无序,试编写一个函数,删除表中所有介于给定两个值(作为函数参数给出)之间的元素的元素结点(若存在)
    思路:遍历单链表,动态维护一个当前结点的前向结点,如果当前结点的元素值满足要求,就删除,时间复杂度为O(n)

  • 给定两个单链表,编写算法找到两个链表的公共结点
    思路:暴力破解,遍历一个链表,每次遍历得到链表值都到另一个链表中遍历寻找是否有对应的值,时间复杂度为O(n*m),这种思路不能说不对,只能说没理解题目的精髓,题目说的是两个链表有公共结点,不仅仅是结点的值相同,而且指针域也要相同,换句话就是遍历找到第一个公共结点后,后面的节点就是重合的,两条链表就完全汇聚成一条,类似于Y结构。因此基于这种精髓的理解,代码写起来复杂度就低很多。

#include "t2.h"

LinkList sameNode(LinkList *L,LinkList *S){
    int len1=0;//L长度
    int len2=0;//S长度

    LNode *p=*L;
    LNode *q=*S;
    // 分别计算两个链表的长度
    while(p->next!=NULL||q->next!=NULL){
        if(p->next!=NULL){
            len1++;
            p=p->next;
        }
        if(q->next!=NULL){
            len2++;
            q=q->next;
        }
    }
    p=*L;
    q=*S;
    // 链表长度的差值,长的先遍历,因为公共链表长度一定是相等的
    while(len1>len2&&p->next!=NULL){
        p=p->next;
        len1--;
    }
    while(len1<len2&&q->next!=NULL){
        q=q->next;
        len2--;
    }
    // 相同长度为0
    if(p->next==NULL||q->next==NULL){
        return NULL;
    }
    while(p->next!=NULL){
        if(p->next->data==q->next->data){
            return p;
        }else{
            p=p->next;
            q=q->next;
        }
    }
    return NULL;
}

有感:算法不是要到出错误才去修改,而是在思考问题的时候,就已经把可能出现的错误给考虑到位,这是需要学习的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值