郝斌数据结构学习笔记(3)链表与练习代码

学习了链式存储结构链表,郝斌数据结构P13-P30

重点内容:

非循环单链表的创建(pTail = pHead设计分离很有意思),

链表遍历(与数组遍历可对应看),

链表插入删除(与数组插入删除可对应看,算法更有意思),

选择排序算法((n-1)*n)[不稳定,冒泡算法更稳定]

 

先放笔记后放代码

离散存储[链表]

预备知识

typedef struct Node

{

int data; //数据本身

struct Node * pNext;  //指向一个跟它数据结构一模一样的另一个结点;存储指向下一个结点的指针

}NODE, PNODE; //NODE等价于struct node, PNODE等价于struct node *

PNODE p = (PNODE)malloc(sizeof(NODE)); //将动态分配的新结点地址分给p

free p; //删除p所指向的结点所占内存,而非p本身内存; 

    //p指向的属于堆内存(程序未使用的内存,程序运行时可用于动态分配内存),p本身属于栈内存(函数中内部声明的变量所占内存)

p->pNext;

 

定义

n个结点离散分配

彼此通过指针相连

每个节点只有一个前驱结点,只有一个后继结点

首结点没有前驱结点,尾结点没有后继结点

 

专业术语:

首结点:

存放第一个有效数据

尾结点:

存放最后一个有效数据

头结点:(头指针就相当于头结点?因为头结点没数据嘛)

头结点的数据类型和首结点类型一样

首结点之前的结点

头结点并不存放有效数据

头结点的目的主要是为了方便对链表的操作 

(头结点的存在,使得空链表和非空链表的处理变得一致,也方便了对链表的开始结点插入或删除操作。)

头指针:

指向头结点的指针变量

尾指针:

指向尾结点的指针变量 //头指针和尾指针一开始是指向的同一个结构体:头结点

  //但是在新增结点之后,头指针指向的还是头结点,但是尾指针指向的就是尾结点了

 

若用一个函数对链表进行处理,至少需要接收链表的哪些参数?

只需要一个参数:头指针

因为通过头指针可以推算出其他所有信息

 

分类

单链表

单向,结点包含两部分:

{数据域

指针域}

双链表

每个结点有两个指针域

 

循环链表

能通过任何一个结点找到其他所有结点

非循环链表

 

算法

遍历

查找

清空

销毁

求长度

排序

删除结点

JAVA的垃圾内存自动释放,但是C和C++的垃圾内存需要手动释放

非循环单链表删除结点伪算法  | … | -> | p | -> | … | -> | … | 

r = p-pNext ;

p->pNext = p->pNext->pNext ;

free(r) ; //释放r指向结点所占内存,而不是释放r本身内存

 

插入结点

非循环单链表插入结点q伪算法  | … | -> | p | -> | … | -> | … | 

方式一: r = p->pNext ; p->pNext = q ; q->pNext = r ;

方式二: q->pNext = p->pNext ; p-pNext = q ;    //不需要临时变量

 

算法

狭义的算法是与数据的存储方式密切相关的

广义的算法是与数据的存储方式无关的

泛型是说,利用某种技术达到的效果是,不同的存储方式,执行的操作是一样的  

 

链表的优缺点:

优点:

插入和删除速度快

不占用连续内存

缺点:

查找速度慢

链表练习代码

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

typedef struct Node
{
    int data;
    struct Node * pNext;
}NODE, *PNODE;

PNODE create_list(void);
void traver_list(PNODE pHead);
bool is_empty(PNODE pHead);
int length_list(PNODE pHead);
void sort_list(PNODE pHead);
bool insert_list(PNODE pHead, int pos, int val);//在pHead所指向的链表的第pos个结点前插入一个值是val新结点
bool delete_list(PNODE pHead, int pos, int *pVal);//在pHead所指向的链表中,删除第pos个结点,并返回该结点的值

int main(void)
{
    PNODE pHead;
    int val;
    pHead = create_list();
    traver_list(pHead);
/*    if(true == is_empty(pHead))
        printf("链表为空!\n");
    else
        printf("链表不为空!\n");
    printf("链表长度为%d\n",length_list(pHead));
    sort_list(pHead);
    traver_list(pHead);*/
    insert_list(pHead, 4, 33);
 /*   if(delete_list(pHead, -1, &val))
    {
        printf("删除成功!删除的结点的值是:%d\n",val);
    }
    else
    {
        printf("删除失败!\n");
    }*/
    traver_list(pHead);

    return 0;
}

PNODE create_list(void)
{
    int len;
    int i;
    int val;
    
    PNODE pHead = (PNODE)malloc(sizeof(NODE));
    if(NULL == pHead)
    {
        printf("动态内存分配失败!\n");
        exit(-1);
    }
    PNODE pTail = pHead;
    pTail->pNext = NULL;
    
    printf("请输入要生成链表的结点数量:len = ");
    scanf("%d",&len);
    
    for(i = 0; i < len; i++)
    {
        printf("请输入结点%d的数据值:",i+1);
        scanf("%d",&val);
        PNODE pNew = (PNODE)malloc(sizeof(NODE));
        if(NULL == pNew)
        {
            printf("动态内存分配失败!\n");
            exit(-1);
        }
        pNew->data = val;
        pTail->pNext = pNew;
        pNew->pNext = NULL;
        pTail = pNew;
    }
    return pHead;
}

void traver_list(PNODE pHead)
{
    if(NULL==pHead) return;
    PNODE p = pHead->pNext;
    while(NULL != p)
    {
        printf("%d ",p->data);
        p = p->pNext;
    }
    printf("\n");
    return;
}

bool is_empty(PNODE pHead)
{
    if(NULL == pHead->pNext)
        return true;
    else
        return false;
}

int length_list(PNODE pHead)
{
    int i = 0;
    PNODE p = pHead->pNext;
    while(NULL != p)
    {
        p = p->pNext;
        i++;
    }
    return i;
}

void sort_list(PNODE pHead)
{
    int i,j,t,len = length_list(pHead);
    PNODE p,q;
    for(i = 0, p = pHead->pNext; i<len-1; i++, p = p->pNext)   //还是之前数组中sort的算法,只不过用链表实现,所以初始化部分、递增部分,链表对标数组元素下标去写
    {                                                                                          //比如i=0表示第一个元素,相当于p=pHead->pNext也表示第一个元素; 比如i++,相当于p=p->pNext
        for(j = i+1, q = p->pNext; j<len; j++, q = q->pNext)   //j = i+1,相当于q=p->pNext;j++,相当于q=q->pNext
        {
            if(p->data > q->data)
            {
                t = p->data;
                p->data = q->data;
                q->data = t;
            }
        }
    }
    return;
}

//其实insert和delete算法在严蔚敏的书中算法步骤里写的挺详细且准确的,比郝斌讲的要好,因为郝斌说他自己没太弄明白

bool insert_list(PNODE pHead, int pos, int val)//核心 插入的话需要找到pos的前一位结点,如果pos是1那就找到头结点,如果pos是4那就找到第三个结点
{
    int i=0;
    PNODE p = pHead;  //因为插入需要用到前一个结点的指针域,所以这里的p只能赋头结点,如果赋首结点的话就没法在首结点前插入了
    while(NULL!=p && i<pos-1) //因为要移动到pos前1位,就是pos-1。当i<pos-1时p持续右移,i初始为0,相当于下标,那最终其实就是移动到a[i] <==> pos-1位置
    {
        p = p->pNext;
        i++;
    }
    
    if(NULL == p || i>pos-1) //判断异常的,如果正常的话此时i肯定等于pos-1,且p有值;除非pos填的值<=1,或者超过链表长度,那p就是NULL了
        return false;
        
    PNODE pNew = (PNODE)malloc(sizeof(NODE));
    if(NULL == pNew)
    {
        printf("动态内存分配失败!\n");
        exit(-1);
    }
    pNew->data = val;
    pNew->pNext = p->pNext;
    p->pNext = pNew;
    return true;
}

bool delete_list(PNODE pHead, int pos, int *pVal)
{
    int i=0;
    PNODE p = pHead;
    while(NULL!=p->pNext && i<pos-1) //删除pos结点,也要找到pos-1结点,修改它的指针域;p即找到的pos-1结点,还要判断该结点后面确实有结点,不能为空
    {
        p = p->pNext;
        i++;
    }
    
    if(NULL == p->pNext || i>pos-1) //判断异常,如果pos-1后面结点为空,那肯定没得删; 经过上个循环后i正常应该等于pos-1,如果此时i>pos-1说明pos值可能<=0,异常的
        return false;
    
    PNODE t = p->pNext;
    *pVal = t->data;
    p->pNext = p->pNext->pNext;
    free(t);
    t = NULL;
    return true;
}

 

https://www.onlinegdb.com/ 该网站在线编译器支持控制台交互,可以调试上面代码

mac linux终端调试代码时需要修改两个头文件

#include <sys/malloc.h>

#include <stdbool.h>
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值