学习了链式存储结构链表,郝斌数据结构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>