第六天2017/04/11(1:结构体链表基础和相关经典操作)

一、结构体基础

【知识回顾】自定义数据结构的本质
这里写图片描述

//结构体中有结构体变量、结构体指针
#include "stdio.h"
#include "stdlib.h"
#include "string.h"

struct student
{
    char name[64];
    int age;
    char *p;
};

struct teacher
{
    char name[64];
    int age;
    char *p;

    struct student s1;  //老师结构体中有学生结构体变量
    struct student *pstu;

    struct teacher *ptea;
};

//struct teacher   //这种定义数据类型的方法是错误的,因为编译器不知道
//{                //这种数据类型所占内存的大小
//  struct teacher tea;  
//};

int main()
{
    struct teacher t1;
    struct student s2;

    t1.age = 40;
    t1.s1.age = 24; 

//我们想通过pstu去操作pstu所指向的内存空间,但是这个内存空间不存在。
//但是没有内存哪有指针!
    //t1.pstu->age = 20;   //没有内存哪有指针?   //正确的使用方法,见下

    t1.pstu = &s2;  //先让pstu指向学生变量s2
    t1.pstu->age = 20;  //在进行赋值
    strcpy(t1.pstu->name,"guojiawei");
    printf("%d,%s\n",t1.pstu->age,t1.pstu->name);

    getchar();
}

二、链表
链表的学习目录
这里写图片描述

链表图
这里写图片描述


//【综合示例】链表的创建、打印、插入、删除、销毁、逆置

#include "stdio.h"
#include "stdlib.h"
#include "string.h"

typedef struct Node
{
    int data;

    struct Node *next;
}SLIST;

//函数的声明   
int create_SList(SLIST** Head) ;
int SList_print(SLIST* pHead);
int SList_Insert(SLIST* pHead,int x,int y);
int SList_Delete(SLIST* pHead,int y);
int SList_Destroy(SLIST* pHead);
int SList_Reverse(SLIST* pHead); //链表的逆置


int main()
{

    SLIST *pHead = NULL;
    if(create_SList(&pHead) != 0)
        return -1;

    printf("打印链表:");
    SList_print(pHead);

//在值为x的结点后面插入值为y的结点
    int x ;
    int y ;
    printf("输入要查找结点为 x 的值 ");
    scanf("%d",&x);
    printf("输入要插入的值 y = ");
    scanf("%d",&y);
    if(SList_Insert(pHead,x,y) != 0)
    {
        return -1;
    }

    printf("在值为x的结点后面插入值为y的结点后,打印链表:\n");
    SList_print(pHead);
//删除结点为y的链表结点
    int rv;
    printf("输入要删除的值 y = ");
    scanf("%d",&y);
    rv = SList_Delete(pHead,y);
    if(rv == 0)
    {
        printf("找到值为y的点,删除的值为y的结点后,打印链表:\n");
        SList_print(pHead);
    }
    else
    {
        printf("没找到值为y的点,直接打印链表:\n");
        SList_print(pHead);
    }
//链表的逆置
    SList_Reverse(pHead);
    printf("链表逆置后,打印链表:");
    SList_print(pHead);

//销毁链表
    SList_Destroy(pHead);

    getchar();
}


//创建链表
int create_SList(SLIST** Head)    //用二级指针作形参,传入的实参是一级指针的地址
{
    int ret = 0;

    int data = 0;
    //1.建立头结点并初始化
    SLIST *pHead = (SLIST*)malloc(sizeof(SLIST));
    if(pHead==NULL)
    {
        ret = -1;
        printf("func create_SList() err:%d",ret);
        return ret;
    }
    pHead->data = 0;
    pHead->next = NULL;

    //2.循环创建结点,结点数据域中的数值从键盘输入,以输入值为-1结束输入
    SLIST *pCur = NULL;  //当前指针:辅助指针
    pCur = pHead;  //准备环境,让pCur指向pHead

    printf("请输入数据:(-1:输入完毕)\n");
    scanf("%d",&data);
    while(data !=-1)
    {
        SLIST *pNew = (SLIST*)malloc(sizeof(SLIST));
        if(pNew == NULL)
        {
            ret = -2;
            SList_Destroy(pHead);  //很重要:能防止内存泄漏
            printf("func:create_SList() err malloc:%d",ret);
            return ret;
        }

        pNew->data = data;

        //①新结点入链表
        pCur->next = pNew; 
        pNew->next = NULL;
        //②当前结点指针pCur下移(新结点变成当前结点)
        pCur = pNew;

        printf("请输入数据:(-1:输入完毕)\n");
        scanf("%d",&data);
    }

    *Head = pHead;  //二级指针作输出的模型
    return ret;
}

//打印链表
int SList_print(SLIST* pHead)  
{
    if(pHead == NULL)
        return -1;

    SLIST* pCur = NULL;
    pCur = pHead->next; //准备环境
    while(pCur)
    {
        printf("%7d",pCur->data);
        pCur = pCur->next;
    }
    printf("\n");
    return 0;
}

//在x结点后插入值为y的结点;如果x结点不在,就把y结点插入到表尾
int SList_Insert(SLIST* pHead,int x,int y)
{
    int ret = 0;
    if(pHead == NULL)  //如果无头结点,则error
    {
        ret = -1;
        printf("func:SList_Insert error %d",ret);
        return ret;
    }
//----------------------------------------------------------------
//若有头,则在该链表中查找值为x的结点
    //环境准备
    SLIST* pPre = pHead;
    SLIST* pCur = pHead->next;
    SLIST* pTmp = NULL;

    while(pCur!=NULL)
    {

        if(pCur->data == x) //如果找到了x
        {
            break;
        }
        //如果没找到x,重置pPre、pCur的位置(把它俩的位置后移一个位置),继续下一次while循环
        pPre = pCur;
        pCur = pCur->next;
    }
//while循环完成后,有下面两种情况:一种是查找到x的结点;一种是没查找到x结点
    //case1:若没查找到x结点(则此时pPre指向链表的最后一个结点; pCur指向最后一个结点的下一个节点即空结点)
    if(pCur == NULL)
    {
        pCur = (SLIST*)malloc(sizeof(SLIST));
        if(pCur == NULL)
        {
            ret = -2;
            printf("func:SList_Insert error: malloc %d",ret);
            SList_Destroy(pHead);
            return ret;
        }
        pCur->data = y;
        pPre->next = pCur;
        pCur->next = NULL;
    }
    else //如果没有找到值为x的结点:此时pCur指向值为x的结点,pPre指向值为x结点的前驱
    {
        SLIST* pNew = (SLIST*)malloc(sizeof(SLIST));
        if(pNew == NULL)
        {
            ret = -2;
            printf("func:SList_Insert error: malloc %d",ret);
            SList_Destroy(pHead);
            return ret;
        }
        pNew->data = y;
        pNew->next = pCur;
        pPre->next = pNew;
    }
    return ret;
}

//删除值为y的结点
int SList_Delete(SLIST* pHead,int y) 
{
//环境准备
    SLIST *pPre = pHead; //保存当前结点pCur的前驱
    SLIST *pCur = pHead->next;

//查找值为y的结点
    while(pCur != NULL)
    {
        if(pCur->data == y)
        {
            break;
        }
        pPre = pCur;
        pCur = pCur->next;
    }
//while循环后,有两种情况:查找到值为y的结点;没查找到值为y的结点
    //1.如果没有找到值为y的结点
    if(pCur == NULL)  
    {
        return -1;  
    }
    else  //2.如果找到值为y的结点:此时pCur指向值为y的结点,pPre指向值为y的结点的前驱
    {
        //删除y结点
        pPre->next = pCur->next;
        free(pCur);
        pCur = NULL;
        return 0;
    }   
}
//销毁链表
int SList_Destroy(SLIST* pHead)
{
    SLIST *pTmp = NULL;  //当前结点的后继
    SLIST *pCur = pHead; //当前结点

    if(pHead == NULL)  //如果是空链表
        return -1;

    while(pCur)
    {
        pTmp = pCur->next;  
        free(pCur);
        pCur = pTmp;
    }
    return 0;
}

int SList_Reverse(SLIST* pHead)  //链表的逆置,注意:使当前指针pCur指向第二个结点
{  
    if(NULL==pHead) //如果传入的头结点为空,则error
    {
        return -1;  //异常返回
    }

//如果不够两个业务结点,则不用逆置链表,直接返回 //即:只有头结点没有业务结点||只有一个业务结点
    if( (NULL==pHead->next)||(NULL == pHead->next->next) ) 
        return 0;  //不用逆置,直接返回
//--------------------------------------------------------------
//如果至少有两个业务结点,则需要逆置链表
    //环境准备
    SLIST *pCur = pHead->next->next; //当前结点直接从第二个业务结点开始
    SLIST *pPre = pHead->next;  //用于保存当前结点的前驱,每次都把pPre与pCur进行交换

    SLIST *pTmp = NULL;  //辅助指针(用来提前保存指向当前结点的后继的指针,缓存指针位置)

    while(pCur != NULL)
    {
    //逆置前,先保存(“缓存”)当前结点的后继(防止断链)
        pTmp = pCur->next; 
    //逆置过程:使当前结点pCur指向它的前驱结点pPre
        pCur->next = pPre;
    //逆置后,让pPre、pCur后移一个位置(重置准备环境)
        pPre = pCur;
        pCur = pTmp;
    }
//while完成后,还剩下头结点和第一个结点的指针域没有处理,下面进行处理
    pHead->next->next = NULL;  //【重点】设置原本的第一个业务结点的指针域为NULL
    pHead->next = pPre;     //把头结点指向原链表的尾结点

    return 0;

//总结:用到3个结构体变量,pPre、pCur、pTmp
    //其中:pPre、pCur用于每次逆置; pTmp用于在逆置前保存pCur的后继(防止断链)
    //逆置完成后pPre指向原链表的最后一个结点
    //【重要】逆置全部完成后,不要忘记两件事:
        //1.把pHead->next = pPre;(把头结点的指针域指向最后一个结点)
        //2.把新链表的尾结点(即:原链表的第一个结点,为pHead->next)设置为NULL,即pHead->next->next = NULL;
}  

/*
链表操作的技巧:
    1.明确    pHead、pPre、pCur、pNext、pTail、pTmp的位置

    1.初始化上面的几个变量:即环境准备
    while(...)  //一般情况下是 while(pCur != NULL)
    {
        2.处理上面的几个变量
        3.重置上面几个变量的位置:即重置环境
    }
    4.退出while时,要“明确一共分为几个情况”以及在每种
      情况下,上面几个变量的位置。
    5.进行最后的操作
*/

链表逆置的解析图
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值