一、gdb调试
gcc -g main.c linklist.c // 对两个.c文件进行编译,生成 a.out 文件
gdb a.out //调试可执行文件 a.out
b linklist.c:36 // 在该.c文件第 36 行设置断点
r // 运行程序,但会在断点前停下
n //执行下一步
s //若遇到函数调用,可通过s进入该函数调用内部执行过程
p len //显示变量值
q //退出调试

二.单链表的基本操作
linklist.h文件 主要是包含数据和函数声明
cpp
#ifndef _LINKLIST_H_ //linklist.h文件,主要包含 数据 和 函数的声明
#define _LINKLIST_H_
typedef struct //链表中数据 的 抽象数据类型
{
char name[10];
char sex;
int age;
int score;
}DATATYPE;
typedef struct _node_ //链表结点声明
{
DATATYPE data;
struct _node_ * next;
}LinkNode;
typedef struct
{
LinkNode * head; //链表头指针
int clen //链表长度
}LinkList;
extern LinkList*CreateLinkList(); //创建链表
extern int InsertHeadLinkList(LinkList*ll,DATATYPE*data);//链表头插
extern int InsertTailLinkList(LinkList*ll,DATATYPE*data);//链表尾插
extern int InsertPosLinkList(LinkList*ll,DATATYPE*data,int pos);//链表指定位置插,从0开始
extern int IsEmptyLinkList(LinkList*ll); //链表判空
extern int GetSizeLinkList(LinkList*ll); //获取链表长度
extern int ShowLinkList(LinkList*ll); //打印链表数据
extern DATATYPE* FindLinkList(LinkList*ll,char*name);//根据名字找链表中的数据
extern int ModifyLinkList(LinkList*ll,char*name,DATATYPE*data); //修改链表数据
extern int DeleteLinkList(LinkList*ll,char*name); //删除链表节点
extern int DestroyLinkList(LinkList**ll); //销毁链表
extern LinkNode* FindMidLinkList(LinkList*ll); //找中间结点
extern LinkNode* FindLastKLinkList(LinkList*ll,int k); //找倒数第k个结点
extern int RevertLinkList(LinkList*ll); //反转结点
extern int InsertSortLinkList(LinkList*ll); //用插入排序,依据成绩对链表排序
#endif
linklist.c文件 主要是包含函数的定义
cpp
#include "linklist.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
LinkList* CreateLinkList() //初始化创建链表
{
LinkList* ll = malloc(sizeof(LinkList)); //为包含头指针的数据结构申请空间
if (NULL == ll) //如果申请失败则,报错并退出
{
fprintf(stderr, "CreateLinkList malloc");
return NULL;
}
ll->head = NULL; //初始指向空
ll->clen = 0; //长度初始为0
return ll;
}
int IsEmptyLinkList(LinkList* ll) //对链表判空
{
return 0 == ll->clen;
}
int InsertHeadLinkList(LinkList* ll, DATATYPE* data) //对链表头插
{
LinkNode* newnode = malloc(sizeof(LinkNode)); //申请一个待插入结点空间,
if (NULL == newnode) //判断是否申请成功
{
fprintf(stderr, "InsertHeadLinkList malloc");
return 1;
}
memcpy(&newnode->data, data, sizeof(DATATYPE)); //将数据拷贝到 新申请结点的数据域中
newnode->next = NULL; //新结点指针域设置为NULL
if (IsEmptyLinkList(ll)) //如果链表为空
{
ll->head = newnode; //直接让头指针指向新结点
}
else
{
newnode->next = ll->head; //否则,新结点指向,头指针指向的下一个结点
ll->head = newnode; // 头指针指向新结点
}
ll->clen++; //结点数量+1
return 0;
}
int GetSizeLinkList(LinkList* ll) //获得结点长度
{
return ll->clen;
}
int ShowLinkList(LinkList* ll) //打印结点
{
int len = GetSizeLinkList(ll);
LinkNode* tmp = ll->head;
int i = 0;
for (i = 0; i < len; ++i)
{
printf("%s %c %d %d\n", tmp->data.name, tmp->data.sex, tmp->data.age,
tmp->data.socre);
tmp = tmp->next;
}
return 0;
}
DATATYPE* FindLinkList(LinkList* ll, char* name) //根据name找结点
{
LinkNode* tmp = ll->head;
while (tmp) //当结点为NULL时,while循环退出
{
if (0 == strcmp(tmp->data.name, name)) //如果相等,则返回该结点数据域,并退出
{
return &tmp->data;
}
tmp = tmp->next; //不相等,则指向下一个结点
}
return NULL; //如果没找到返回NULL
}
int DeleteLinkList(LinkList* ll, char* name) //根据数据域中的名字,删除结点
{
LinkNode* tmp = ll->head;
if (IsEmptyLinkList(ll)) //如果链表为空,直接返回1
{
return 1;
}
if (0 == strcmp(tmp->data.name, name)) //如果第一个就是
{
ll->head = ll->head->next;
free(tmp); //释放结点
ll->clen--;
return 0;
}
while (tmp->next) //当下一个节点为空时退出,因此上面把第一个结点单独拿出来写
{
if (0 == strcmp(tmp->next->data.name, name))
{
LinkNode* tmp2 = tmp->next;
tmp->next = tmp->next->next;
free(tmp2);
ll->clen--;
return 0; //找到返回0
}
tmp = tmp->next;
}
return 1; //未找到返回1
}
int InsertTailLinkList(LinkList* ll, DATATYPE* data) //尾插,与头插类似
{
if (IsEmptyLinkList(ll)) //链表如果为空
{
return InsertHeadLinkList(ll, data); //直接调头插
}
else
{
LinkNode* newnode = malloc(sizeof(LinkNode));
if (NULL == newnode)
{
fprintf(stderr, "InsertTailLinkList malloc");
return 1;
}
memcpy(&newnode->data, data, sizeof(DATATYPE));
newnode->next = NULL;
LinkNode* tmp = ll->head;
while (tmp->next)
{
tmp = tmp->next; // tmp++;
}
tmp->next = newnode;
ll->clen++;
}
return 0;
}
int InsertPosLinkList(LinkList* ll, DATATYPE* data, int pos) //指定位置插入数据
{
int len = GetSizeLinkList(ll); //得到链表长度
if (pos < 0 || pos > len) //如果插入位置不在该区间内,直接返回1,说明错误
{
return 1;
}
if (0 == pos) // head 头部插入
{
return InsertHeadLinkList(ll, data);
}
else if (pos == len) // tail 尾部插入
{
return InsertTailLinkList(ll, data);
}
else
{
LinkNode* newnode = malloc(sizeof(LinkNode)); 申请结点
if (NULL == newnode)
{
fprintf(stderr, "InsertPosLinkList malloc");
return 1;
}
memcpy(&newnode->data, data, sizeof(DATATYPE));
newnode->next = NULL;
int i = 0;
LinkNode* tmp = ll->head;
for (i = 0; i < pos - 1; ++i) 让指针指向要插入位置的前一个结点
{
tmp = tmp->next;
}
newnode->next = tmp->next;
tmp->next = newnode;
}
ll->clen++;
return 0;
}
int ModifyLinkList(LinkList* ll, char* name, DATATYPE* data) //修改指针中的数据
{
DATATYPE* tmp = FindLinkList(ll, name);
if (NULL == tmp)
{
return 1;
}
memcpy(tmp, data, sizeof(DATATYPE));
return 0;
}
int DestroyLinkList(LinkList** ll) //销毁链表
{
while (1) //需要先一个一个结点free最后free(linklist)
{
LinkNode* tmp = (*ll)->head;
if (NULL == tmp)
{
break;
}
(*ll)->head = (*ll)->head->next;
free(tmp);
}
free(*ll);
*ll = NULL;
return 0;
}
//可通过 valgrind ./执行文件名 看内存是否泄露,即没有free干净
LinkNode* FindMidLinkList(LinkList* ll) //找链表中间结点
{
LinkNode* fast = ll->head; //申请两个指针,一个一次走一步,一个一次走两步,走两步的走完链表时
LinkNode* slow = ll->head; //,走一步的指针刚好到中间。
while (fast)
{
fast = fast->next;
if (NULL == fast)
{
break;
}
slow = slow->next;
fast = fast->next;
}
return slow;
}
/**
* @brief 查找倒数第k个节点
*
* @param ll 需要查找的对应的链表
* @param k 倒数第k个节点 k>0 <len
* @return LinkNode* 找到对应的节点的指针
*/
LinkNode* FindLastKLinkList(LinkList* ll, int k) //与找中间的类似,只是,走的快的和走的慢的指针
{
LinkNode* slow = ll->head; //始终保持 k 个结点距离
LinkNode* fast = ll->head;
for (int i = 0; i < k; i++)
{
fast = fast->next;
}
while (fast)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
int RevertLinkList(LinkList* ll) //逆序链表结点
{
LinkNode* prev = NULL; //指向 逆序结点的前一个结点
LinkNode* tmp = ll->head; //指向 逆序的结点
LinkNode* next = tmp->next; //指向 逆序结点的下一个结点
int len = GetSizeLinkList(ll);
if (len < 2) //如果链表长度 <2 则不需要逆序
{
return 1;
}
while (1)
{
tmp->next = prev;
prev = tmp;
tmp = next;
if (NULL == tmp) //当指向 需要逆序的结点指针为空时,说明没有结点了,跳出循环
break;
next = next->next;
}
ll->head = prev;
return 0;
}
int InsertSortLinkList(LinkList* ll) //对链表进行插入排序,依据年龄
{
LinkNode*pins = ll->head; //三个指针,pins指向头结点,ptmp指pins指向结点的下一个
LinkNode* ptmp = pins->next; //Next指向pins指向结点的下一个结点。
LinkNode* Next = ptmp->next;
ptmp->next = NULL; //置空
ll->head->next = NULL; //置空
while(1)
{
pins = ll->head;
while(pins->next && ptmp->data.age > pins->data.age ) //pins的下一个结点存在 并且下一个
{ //结点的age > pins 的age 则pins指向下一个
pins = pins->next;
}
if(pins->data.age > ptmp->data.age) //head 如果第是头节点的下一个结点需要插入排序
{
ptmp->next = ll->head;
ll->head = ptmp;
}
else
{
ptmp->next = pins->next;
pins->next = ptmp;
}
ptmp = Next;
if(NULL == ptmp) { break; }
Next = Next->next;
ptmp->next = NULL;
}
return 0;
}
三、顺序表和链表对比
1、存储方式
顺序表 是一段连续的存储单元
链表 是逻辑结构连续物理结构(在内存中的表现形式)不连续(因为是通过malloc获得的)
2、 时间性能
查找 : 顺序表O(1) //按规则放,找的时候按规则找;支持随机访问
链表 O(n) //只能从第一个往后找
插入和删除:顺序表 O(n) //整体前移与后移
链表 O(1) //一次性改(只需把一个节点连上),与链表数据量无关
3、 空间性能
顺序表 需要预先分配空间,大小固定;只放数据
链表, 不需要预先分配,大小可变,动态分配;数据 + 指针域