1. 数据结构
1.1 定义
数据结构是指计算机中数据的组织、管理和存储方式。它不仅包括数据元素的存储方式,还包括数据元素之间的关系,以及对数据进行操作的方法和算法。数据结构的选择和设计直接影响算法的效率和程序的性能,是计算机科学与编程中非常重要的基础
1.2 数据结构的分类
(1)逻辑角度
1. 线性结构(一对一)数组、链表、队列、栈
2. 树型结构(一对多)二叉树
3. 图形结构(多对多)网状结构
(2)存储角度
1. 顺序存储:采用一段连续的内存空间保存元素(数组)
优点:空间连续,访问方便
缺点:插入删除需要移动大量元素、需要预分配内存空间,容易造成存储空间碎片
2. 链式存储:采用一组非连续的内存空间保存元素(链表)
优点:插入和删除数据方便、不需要预分配内存
缺点:访问元素效率低
3. 散列存储(哈希存储)将数据元素的存储位置与关键码之间建立确定对应关系从而实现查找的存储方式
4. 索引存储:通过关键字构建索引表、通过索引表来找到数据的存储位置
补充:
1. 重点学习内容
顺序表、链式表、顺序栈、链式栈、顺序队列、链式队列、二叉树、哈希表
2. 程序 = 数据结构 + 算法
2. 分析算法效率的两个指标
2.1 时间复杂度
是指算法在执行过程中所需时间的量度。它衡量的是算法的执行时间随输入规模的变化情况,通常用大O记号表示,如O(n)、O(log n)等
2.2 空间复杂度
是指算法在执行过程中所需存储空间的量度。它衡量的是算法的内存使用情况,通常也用大O记号表示。
3.无头单向链表代码
3.1 makefile
OBJ:=seqlist
OBJS+=SeqList.c
CC:=gcc $(OBJ):$(OBJS)$(CC) $^ -o $@
.PHONY:
clean:rm $(OBJ)
test:valgrind --tool=memcheck --leak-check=full ./$(OBJ)
3.2 头文件
#ifndef _LINKLIST_H_
#define _LINKLIST_H_#include <stdio.h>
#include <stdlib.h>
#include <string.h>typedef int DataType;typedef struct node
{DataType Data;struct node *pNext;
}LinkNode;typedef struct list
{LinkNode *pHead;int cLen;
}LinkList;extern LinkList *create_LinkList(void);
extern int is_empty_link(LinkList *pTmpList);
extern int push_head_link(LinkList *pTmpList, DataType Data);//头插、空链表也能使用
extern int push_tail_link(LinkList *pTmpList, DataType Data);//尾插、空链表也能使用
extern void link_for_each(LinkList *pTmpList);
extern int pop_head_link(LinkList *pTmpList);
extern int pop_tail_link(LinkList *pTmpList);
extern LinkNode *find_data(LinkList *pTmpList, DataType Data);
extern int modify_data(LinkList *pTmpList, DataType old_data, DataType new_data);
extern void destory_link(LinkList *pTmpList);
extern LinkNode *find_mid_node(LinkList *pTmpList);//找中间节点
extern LinkNode *find_last_k_node(LinkList *pList, int k);//寻找倒数第k个节点
extern int pop_data_link(LinkList *pTmpList, DataType Data);//删除数据为key的节点
extern void invert_link(LinkList *pTmpList);
extern void insert_sort_link(LinkList *pTmpList);#endif
3.3 主函数
#include "LinkList.h"LinkList *create_LinkList(void)//创建空链表
{LinkList *pList = NULL;pList = malloc(sizeof(LinkList));if (NULL == pList){perror("fail to malloc");return NULL;}pList->pHead = NULL;pList->cLen = 0;return pList;
}int is_empty_link(LinkList *pTmpList)//判断链表是否为空链表
{if (NULL == pTmpList->pHead){return 1;}return 0;
}int push_head_link(LinkList *pTmpList, DataType Data)//头插、空链表也能使用
{LinkNode *pTmpNode = NULL;pTmpNode = malloc(sizeof(LinkNode));if (NULL == pTmpNode){perror("fail to malloc");return -1;}pTmpNode->pNext = NULL;//插入节点初始化pTmpNode->Data = Data;pTmpNode->pNext = pTmpList->pHead;pTmpList->pHead = pTmpNode;pTmpList->cLen++;return 0;
}int push_tail_link(LinkList *pTmpList, DataType Data)//尾插、空链表也能使用
{LinkNode *pTmpNode = NULL;LinkNode *pLastNode = NULL;pTmpNode = malloc(sizeof(LinkNode));if (NULL == pTmpNode){perror("fail to malloc");return -1;}pTmpNode->pNext = NULL;pTmpNode->Data = Data;if (is_empty_link(pTmpList)){pTmpList->pHead = pTmpNode;}else {pLastNode = pTmpList->pHead;while (pLastNode->pNext != NULL){pLastNode = pLastNode->pNext;}pLastNode->pNext = pTmpNode;}pTmpList->cLen++;return 0;
}void link_for_each(LinkList *pTmpList)//遍历输出链表
{LinkNode *pTmpNode = NULL;pTmpNode = pTmpList->pHead;while (pTmpNode != NULL){printf("%d ", pTmpNode->Data);pTmpNode = pTmpNode->pNext;}putchar('\n');
}int pop_head_link(LinkList *pTmpList)//头删
{if (is_empty_link(pTmpList)){return -1;}LinkNode *pTmpNode = NULL;pTmpNode = pTmpList->pHead;pTmpList->pHead = pTmpNode->pNext;free(pTmpNode);pTmpList->cLen--;return 0;
}int pop_tail_link(LinkList *pTmpList)//尾删
{if (is_empty_link(pTmpList)){return -1;}if (1 == pTmpList->cLen){pop_head_link(pTmpList);}else{LinkNode *pTmpNode = NULL;pTmpNode = pTmpList->pHead;while (pTmpNode->pNext->pNext != NULL){pTmpNode = pTmpNode->pNext;}free(pTmpNode->pNext);pTmpNode->pNext = NULL;pTmpList->cLen--;}return 0;
}LinkNode *find_data(LinkList *pTmpList, DataType Data)//在链表中寻找数据
{LinkNode *pTmpNode = NULL;pTmpNode = pTmpList->pHead;while (pTmpNode){if (Data == pTmpNode->Data){return pTmpNode;}pTmpNode = pTmpNode->pNext;}return NULL;
}int modify_data(LinkList *pTmpList, DataType old_data, DataType new_data)//修改数据
{LinkNode *pTmpNode = NULL;pTmpNode = find_data(pTmpList, old_data);if (pTmpNode != NULL){pTmpNode->Data = new_data;return 0;}else {return -1;}
}void destory_link(LinkList *pTmpList)//销毁链表
{while (!is_empty_link(pTmpList)){pop_head_link(pTmpList);}free(pTmpList);
}LinkNode *find_mid_node(LinkList *pTmpList)//找中间节点
{LinkNode *pSlowNode = NULL;LinkNode *pFastNode = NULL;pFastNode = pTmpList->pHead;pSlowNode = pFastNode;while (pFastNode != NULL){pFastNode = pFastNode->pNext;if (NULL == pFastNode){break;}pFastNode = pFastNode->pNext;//pFastNode 速度是 pSlowNode的两倍pSlowNode = pSlowNode->pNext;}return pSlowNode;
}LinkNode *find_last_k_node(LinkList *pTmpList, int k)//寻找倒数第k个节点
{LinkNode *pFastNode = NULL;LinkNode *pSlowNode = NULL;int i = 0;pFastNode = pTmpList->pHead;pSlowNode = pFastNode;for (i = 0; i < k; i++)//快慢指针相差k个位置{if (NULL == pFastNode){return NULL;}pFastNode = pFastNode->pNext;}while (pFastNode != NULL){pFastNode = pFastNode->pNext;pSlowNode = pSlowNode->pNext;}return pSlowNode;
}int pop_data_link(LinkList *pTmpList, DataType Data)//删除数据为key的节点
{if (is_empty_link(pTmpList)){return 1;}LinkNode *pTmpNode = NULL;LinkNode *pPreNode = NULL;pTmpNode = pTmpList->pHead;while (pTmpNode != NULL){if (Data == pTmpNode->Data)//找到数据{if (pTmpNode == pTmpList->pHead)//判断是否只有头结点{pTmpList->pHead = pTmpNode->pNext;free(pTmpNode);pTmpNode = pTmpList->pHead;}else{pPreNode->pNext = pTmpNode->pNext;free(pTmpNode);pTmpNode = pPreNode->pNext;}pTmpList->cLen--;}else{pPreNode = pTmpNode;pTmpNode = pTmpNode->pNext;}}
}void invert_link(LinkList *pTmpList)//链表的倒置
{LinkNode *pTmpNode = NULL;LinkNode *pInsertNode = NULL;pTmpNode = pTmpList->pHead;pTmpList->pHead = NULL;while (pTmpNode != NULL){pInsertNode = pTmpNode;pTmpNode = pTmpNode->pNext;pInsertNode->pNext = pTmpList->pHead;pTmpList->pHead = pInsertNode;}
}void insert_sort_link(LinkList *pTmpList)
{if (is_empty_link(pTmpList) || NULL == pTmpList->pHead->pNext)//空链表或者只有一个节点,不用排序{return ;}LinkNode *pTmpNode = NULL;//记录剩余节点的起始位置LinkNode *pInsertNode = NULL;//插入节点的位置LinkNode *p = NULL;//插入排序中遍历已经插入的节点的指针pTmpNode = pTmpList->pHead->pNext;//第一个节点不排序,从第二个节点开始排序pTmpList->pHead->pNext = NULL;//从原链表的第一个节点之后断开while (pTmpNode != NULL)//判断剩余未排序节点是否存在,最后一个节点参与操作{pInsertNode = pTmpNode;pTmpNode = pTmpNode->pNext;if (pInsertNode->Data <= pTmpList->pHead->Data)//判断要插入节点数据的大小是否小于第一个节点的数据{pInsertNode->pNext = pTmpList->pHead;//头插pTmpList->pHead = pInsertNode;}else //往后插{p = pTmpList->pHead;while (p->pNext != NULL && p->pNext->Data < pInsertNode->Data){p = p->pNext;}pInsertNode->pNext = p->pNext;p->pNext = pInsertNode;}}
}int main(void)
{LinkList *pList = NULL;LinkNode *pTmpNode = NULL;pList = create_LinkList();//创建if (NULL == pList){return -1;}
#if 0push_head_link(pList, 3);//头插push_head_link(pList, 2);push_head_link(pList, 1);link_for_each(pList);
#endifpush_tail_link(pList, 1);//尾插push_tail_link(pList, 2);push_tail_link(pList, 3);push_tail_link(pList, 4);push_tail_link(pList, 5);push_tail_link(pList, 6);push_tail_link(pList, 7);link_for_each(pList);
#if 0pop_head_link(pList);//头删link_for_each(pList);pop_tail_link(pList);//尾删link_for_each(pList);
#endifpTmpNode = find_data(pList, 4);//找数据if (NULL != pTmpNode){printf("find node data = %d\n", pTmpNode->Data);}else{printf("not find this node\n");}modify_data(pList, 2, 20);//修改数据modify_data(pList, 3, 30);modify_data(pList, 4, 40);modify_data(pList, 5, 50);link_for_each(pList);pTmpNode = find_mid_node(pList);//找链表中间节点if (pTmpNode != NULL){printf("Mid node data = %d\n", pTmpNode->Data);}link_for_each(pList);pTmpNode = find_last_k_node(pList, 5);//寻找链表倒数第k个节点if (pTmpNode != NULL){printf("last node data = %d\n", pTmpNode->Data);}link_for_each(pList);pop_data_link(pList, 30);//删除链表数据link_for_each(pList);invert_link(pList);//链表倒置link_for_each(pList);insert_sort_link(pList);link_for_each(pList);destory_link(pList);//销毁链表return 0;
}
4. 快慢双指针解决链表问题
4.1 寻找中间节点——(后面的指针前进速度是前面指针的一半)
LinkNode *find_mid_node(LinkList *pTmpList)//找中间节点
{LinkNode *pSlowNode = NULL;LinkNode *pFastNode = NULL;pFastNode = pTmpList->pHead;pSlowNode = pFastNode;while (pFastNode != NULL)//要操作pFastNod{pFastNode = pFastNode->pNext;if (NULL == pFastNode){break;}pFastNode = pFastNode->pNext;//pFastNode 速度是 pSlowNode的两倍pSlowNode = pSlowNode->pNext;}return pSlowNode;
}
4.2 寻找倒数第k个节点——(两指针相差k)
LinkNode *find_last_k_node(LinkList *pTmpList, int k)//寻找倒数第k个节点
{LinkNode *pFastNode = NULL;LinkNode *pSlowNode = NULL;int i = 0;pFastNode = pTmpList->pHead;pSlowNode = pFastNode;for (i = 0; i < k; i++)//快慢指针相差k个位置{if (NULL == pFastNode)//节点数不足k程序直接结束{return NULL;}pFastNode = pFastNode->pNext;}while (pFastNode != NULL){pFastNode = pFastNode->pNext;pSlowNode = pSlowNode->pNext;}return pSlowNode;
}
4.3 删除数据为key的节点
int pop_data_link(LinkList *pTmpList, DataType Data)//删除数据为key的节点
{if (is_empty_link(pTmpList)){return -1;}LinkNode *pTmpNode = NULL;LinkNode *pPreNode = NULL;pTmpNode = pTmpList->pHead;while (pTmpNode != NULL){if (Data == pTmpNode->Data)//找到数据{if (pTmpNode == pTmpList->pHead)//判断是否只有头结点{pTmpList->pHead = pTmpNode->pNext;free(pTmpNode);pTmpNode = pTmpList->pHead;}else{pPreNode->pNext = pTmpNode->pNext;free(pTmpNode);pTmpNode = pPreNode->pNext;}pTmpList->cLen--;}else{pPreNode = pTmpNode;pTmpNode = pTmpNode->pNext;}}
}
5. 单向链表算法
5.1 链表的倒置
void invert_link(LinkList *pTmpList)//链表的倒置
{LinkNode *pTmpNode = NULL;LinkNode *pInsertNode = NULL;pTmpNode = pTmpList->pHead;pTmpList->pHead = NULL;//断开链表while (pTmpNode != NULL){pInsertNode = pTmpNode;pTmpNode = pTmpNode->pNext;pInsertNode->pNext = pTmpList->pHead;pTmpList->pHead = pInsertNode;}
}
5.2 链表的插入排序(从小到大)
void insert_sort_link(LinkList *pTmpList)//插入排序法
{if (is_empty_link(pTmpList) || NULL == pTmpList->pHead->pNext)//空链表或者只有一个节点,不用排序{return ;}LinkNode *pTmpNode = NULL;//记录剩余节点的起始位置LinkNode *pInsertNode = NULL;//插入节点的位置LinkNode *p = NULL;//插入排序中遍历已经插入的节点的指针pTmpNode = pTmpList->pHead->pNext;//第一个节点不排序,从第二个节点开始排序pTmpList->pHead->pNext = NULL;//从原链表的第一个节点之后断开while (pTmpNode != NULL)//判断剩余未排序节点是否存在,最后一个节点参与操作{pInsertNode = pTmpNode;pTmpNode = pTmpNode->pNext;if (pInsertNode->Data <= pTmpList->pHead->Data)//判断要插入节点数据的大小是否小于第一个节点的数据{pInsertNode->pNext = pTmpList->pHead;//头插pTmpList->pHead = pInsertNode;}else //往后插{p = pTmpList->pHead;while (p->pNext != NULL && p->pNext->Data < pInsertNode->Data){p = p->pNext;}pInsertNode->pNext = p->pNext;p->pNext = pInsertNode;}}
}
6. 测试链表是否被销毁
valgrind ./a.out——查看开辟的空间是否全部被释放