文章目录
程序的本质 = 数据结构 + 算法
第一章 数据结构
1.1 数据结构的定义
数据结构( Data Structure) 是数据的组织方式。程序中用到的数据都不是孤立的,而是有相互联系的,根据访问数据的需求不同,同样的数据可以有多种不同的组织方式。
以前学过的复合类型也可以看作数据的组织方式,把同一类型的数据组织成数组, 或者把描述同一对象的各成员组织成结构体。数据的组织方式包含了存储方式和访问方式这两层意思,二者是紧密联系的。
例如,数组的各元素是一个挨一个存储的,并且每个元素的大小相同,因此数组可以提供按下标访问的方式,结构体的各成员也是一个挨一个存储的,但是每个成员的大小不同,所以只能用运算符加成员名来访问,而不能按下标访问。
一般来说,在处理数据时,什么样的数据结构,就会影响我们采用什么样的算法来处理数据。
1.2 数据结构的逻辑分类与物理存储
1.2.1 线性的物理储存
已经说过很多遍啦,我们的内存空间是线性的:
1.2.2 逻辑分类
我们平时所说的线性,非线性,其实就是逻辑上的分类。物理上有可能不是线性的。
1.2.2.1 线性
数据原本就是线性的,数组,链表。数组在物理内存上也是一个一个连续储存,也是线性的。
1.2.2.2 非线性
数据结构本身如果是链式的,这时候在储存上往往也是链式的,也就是非线性。比如树和图。
第二章 链表 (List)
2.1 现实意义
不管是堆上,亦或是栈上,我们去申请大容量的连续内存的时候,即使是有空闲内存,由于内存碎片,往往也很容易申请失败(尤其是堆上)。链式结构的出现容许我们使用一连串不连续的内存空间来储存数据。
2.3 ADT
ADT(Abstract Data Type) ,即抽象数据类型,是一种从学术研究层面抽象的方法结果集, 表示形式。了解了解就好,没有太多实际上的用处。
2.4 实现
2.4.1 单向链表
#include "MyList.h"
#include <iostream>
using namespace std;
NodeList* CreateLIst()
{
//创建头节点,申请空间;
NodeList* head = (NodeList*)malloc(sizeof(NodeList));
if (head == nullptr)
{
cout << "Malloc Erro" << endl;
exit(-1);
}
head->data = 0;
head->next = nullptr;
return head;
}
void InsertList(NodeList* head, int newData)
{
NodeList* cur = (NodeList*)malloc(sizeof(NodeList));
cur->data = newData;
cur->next = head->next;//让新来的节点有所指向;
head->next = cur;
}
void TravereList(NodeList* head)
{
head = head->next;
if (!head)
{
cout << "链表未生成";
}
while (head)
{
cout << head->data << " ";
head = head->next;
}
cout << endl;
}
int LenList(NodeList* head)
{
int length = 0;
head = head->next;
for (head; head; length++, head = head->next);
/*while (head)
{
length++;
head = head->next;
}*/
return length;
}
NodeList* SearchList(NodeList* head, int find)
{
head = head->next;
while(head)
{
if(head->data == find)
break;
head = head->next;
}
return head;
}
void DeleteList(NodeList* head, NodeList* pFind)
{
if (pFind->next == nullptr)
{
while (head->next!=pFind)
head = head->next;
head->next = pFind->next;
pFind->next = nullptr;
free(pFind);
}
else
{
pFind->data = pFind->next->data;
NodeList* temp = pFind->next;
pFind->next = temp->next;
free(temp);
}
/*while (head->next!=pFind)
{
head = head->next;
}
head->next = pFind->next;
pFind->next = nullptr;
free(pFind);*/
}
void PopSortList(NodeList* head)
{
/*NodeList* p, * q;
int len = LenList(head);
for (int i = 0; i < len - 1; i++)
{
p = head->next;
q = p->next;
for (int j = 0; j < len - 1 - i; j++)
{
if (p->data > q->data)
{
p->data ^= q->data;
q->data ^= p->data;
p->data ^= q->data;
}
p = p->next;
q = q->next;
}
}*/
NodeList* p, * q, * pre;
int len = LenList(head);
for (int i = 0; i < len - 1; i++)
{
pre = head;
p = head->next;
q = p->next;
for (int j = 0; j < len - 1 - i; j++)
{
if (p->data > q->data)
{
pre->next = q;
p->next = q->next;
q->next = p;
q = p->next;
pre = pre->next;
continue;
}
pre = pre->next;
p = p->next;
q = q->next;
}
}
}
void ReverseList(NodeList* head)
{
NodeList* newHead = head->next;
head->next = nullptr;
NodeList* tmp = nullptr;
while (newHead)
{
tmp= newHead->next;
newHead->next = head->next;
head->next = newHead;
newHead = tmp;
}
}
void DestoryList(NodeList* head)
{
NodeList* cur;
while (head)
{
cur = head->next;
free(head);
head = cur;
}
}
2.4.2 双向链表
2.5 应用
2.5.1 逆序一个链表
2.5.2 如何判断一个链表是否有环
套圈原理。
2.5.3 求链表的中间节点,要求只用一重循环
2.5.4 约瑟夫环
第三章 栈 (Stack)
3.1 现实意义
递归的问题,本质就是函数压栈。如果利用栈工具,递归的操作,就可以完全用循环加栈的组合方式来实现。
3.2 基本概念
3.3 ADT
3.4 实现
3.4.1 线式储存实现
3.4.1.1 MyStack.h
typedef struct _MyStack {
int top;
int _length;
char* _space;
}MyStack;
void InitStack(MyStack* p,int size)
{
p->top = 0;
p->_length = size;
p->_space = (char*)malloc(sizeof(char) * p->_length);
}
bool IfStackFull(MyStack* p)
{
return p->top == p->_length;
}
bool IfStackEmpty(MyStack* p)
{
return p->top == 0;
}
void Push(MyStack* p, char ch)
{
p->_space[p->top++] = ch;
}
char Pop(MyStack* p)
{
return p->_space[--p->top];
}
void ResetStack(MyStack* p)
{
p->top = 0;
}
void CleanStack(MyStack* p)
{
free(p->_space);
}
3.4.2 链式储存实现
设计时选择表头作为栈顶指针,而不是表尾(单向链表(不含头节点))。不同于线式存储,不需要作判满操作。
typedef struct _Node {
char data;
struct _Node* next;
}Node;
typedef struct _ListStack {
Node* top;
}ListStack;
void InitListStack(ListStack* p)
{
p->top = (Node*)malloc(sizeof(Node));
p->top->next = nullptr;
}
bool IsListStackEmpty(ListStack* p)
{
return p->top->next == nullptr;
}
void ListStackPush(ListStack* p, char ch)
{
Node * cur = (Node*)malloc(sizeof(Node));
cur->data = ch;
cur->next = p->top->next;
p->top->next = cur;
}
char ListStackPop(ListStack* p)
{
Node* cur = p->top->next;
char ch = cur->data;
p->top->next = cur->next;
free(cur);
return ch;
}
void ResetListStack(ListStack* p)
{
while (!IsListStackEmpty(p))
{
ListStackPop(p);
}
}
void ClearListStack(ListStack* p)
{
ResetListStack(p);
free(p->top);
}
链式结构大多数情况下无需判满。
3.5 应用
3.5.1 一组双栈
用一个数组表示两个堆栈,最大限度的利用空间,分别用数组的两端作为两个栈的起点,向中间扩展,两个栈中的元素总和不超过n时,两个栈不会相遇。
3.5.2 深度优先搜索 (打印地图版)
小弟:大哥,有岔路,往哪里走。
大哥:管它岔路不岔路,做个标记选一条走就完事了!不通再回来!
3.5.2.1 图示
#include <iostream>
#include <windows.h>
#include "Stack.h"
using namespace std;
#define MAXROW 10
#define MAXLINE 10
ListStack s;
int maze[MAXROW][MAXLINE] = {
1,1,1,1,1,1,1,1,1,1,
0,0,0,1,1,1,1,1,1,1,
1,1,0,1,1,1,1,1,1,1,
1,1,0,0,0,0,1,1,1,1,
1,1,0,1,1,0,1,1,1,1,
1,1,0,1,1,0,1,1,1,1,
1,1,1,1,1,0,1,1,1,1,
1,1,1,1,1,0,0,0,1,1,
1,1,1,1,1,1,1,0,0,0,
1,1,1,1,1,1,1,1,1,1
};
void DisplyMaze()
{
for (int i = 0; i < MAXROW; i++)
{
for (int j = 0; j < MAXLINE; j++)
{
if (maze[i][j] == 1)
printf("%2s", "*"); //墙
else if(maze[i][j] == 2)
printf("%2s", "#"); //走过的路径
else
printf("%2s", "");//路
} putchar(10);
} printf("====================\n");
}
void Visit(int x, int y)
{
Point p = {
x,y };
ListStackPush(&s,p);
}
int main(int argc, char** argv)
{
Point sp{
1,0 }, ep{
8,9 };//起始点
InitListStack(&s);
ListStackPush(&s,sp);
int flag = 1;
while (!IsListStackEmpty(&s) && flag)
{
Point t;
t = ListStackPop(&s);
cout << "(" << t._x << ", " << t._y << ")" << endl;
maze[t._x][t._y] = 2;
system("cls");
DisplyMaze();
Sleep(100);
//上
if (t._x - 1 >= 0 && maze[t._x - 1][t._y] != 2 && maze[t._x - 1][t._y] != 1) //(判定边界;判定墙;判定是否走过)
Visit(t._x - 1, t._y);
//下
if (t._x + 1 <= 9 && maze[t._x + 1][t._y] != 2 && maze[t._x + 1][t._y] != 1)
Visit(t._x + 1, t._y);
//左
if (t._y - 1 >= 0 && maze[t._x][t._y-1] != 2 && maze[