数据结构(软件) 操作系统——————————>计算机网络 计算机组成(硬件)
数据概念基础
数据元素: 即:数据的基本单位,数据元素包括数据项,多个数据元素形成的集合就是数据对象,数据对象具有相同性质的数据元素的集合。同一个数据对象中的数据元素可以组成不同的数据结构(线性或者网状)
数据结构的三要素:
数据类型: 即:一个值得集合和定义在此集合上的一组操作的总称。 1)原子类型。其值不可以再分的数据类型,如 bool 类型 2)结构类型。其值可以在分解为若干成分的数据类型,如int类型
抽象数据类型(ADT): 即:抽象数据组织及与之相关的操作。
算法: (1)五个特性:有穷性、确定性、可行性、输入、输出 (2)好的算法:正确性、可读性、健壮性、高效与低存储:省时、省内存;时间复杂度低、空间复杂度低
算法效率的度量:时间复杂度、空间复杂度
- 时间复杂度
能够事先预估算法的**时间开销**T(n)与问题的规模 n 的关系; 在考虑时,只考虑时间开销的最高次项(同时不考虑最高项的系数),只需要考虑O(n)数量级
根据高数知识: 实际工程中: 结论1:顺序执行的代码只会影响常数项,可以忽略; 结论2:只需挑循环中的一个基本操作分析它的执行次数与n的关系即可。同时,外层循环执行n次,嵌套两层循环,则内层共执行 n^2 次。 结论3:如果有多层嵌套循环,只需关注最深层循环了几次。
一般考虑:最坏时间复杂度,平均时间复杂度
- 空间复杂度
讨论**空间开销**(内存开销)与问题规模 n 之间的关系
(1)程序代码,大小固定,与问题规模无关。 (2)无论问题规模怎么变,算法运行所需的内存空间都是固定的常量 ,算法空间复杂度为S(n)=O(1),则称该算法可以原地工作 (3)对于函数递归调用时,带来的内容开销,即当每次递归值定义变量 S(n)=O(n),即此时 空间复杂度 = 递归调用的深度 当每次递归内容定义一个数组n,递归次数为n,即S(n)=O(n^2)。
线性表
逻辑结构与基本操作
性质: (1)相同数据类型 (2)有限序列 (3)n为表长
概念: ai 是线性表中的“第i个元素”线性表中的位序; a1 是 表头元素;an是表尾元素 直接前驱,直接后继
基本操作: (1)初始化表:构建一个空的线性表,分配内从空间 (2)销毁操作:销毁线性表,并释放线性表所占用的内存空间 (3)插入操作:在表中的第i个位置上插入指定的元素e; (4)删除操作:删除表中第i个位置的元素,并用e返回删除元素的值; (5)按值查找操作:在表中查找查找具有给定关键字的个数; (6)按位值找操作:获取表中的第i个位置的元素值
(7)求表长,返回线性表的长度, (8)输出操作,按前后顺序输出线性表的所有元素 (9)判空操作,如表为空,则返回true,否则false;
存储/物理结构
- 顺序表(顺序存储)
用线性存储的方式实现的线性表。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来实现。sizeof(类型)函数求存储空间大小
特点: (1)随机访问:能在O(1)时间内找到第 i 个元素 * (2)存储密度高 (3)拓展容量不方便 (4)插入、删除数据元素不方便
静态分配:数组 动态分配: C – malloc 、free 函数 即 malloc 申请一片连续存储空间,free 释放空间
顺序表的定义与动态调整
#include<stdio.h>
int *data = NULL;
data = (int*)malloc(sizeof(int) * 10);
#include "cstdio"
#define InitSize 10
struct MyStruct
{
int *data;
int MaxSize;
int length;
};
int InitList(MyStruct &L)
{
L.data = (int*)malloc(InitSize * sizeof(int));
L.length = 0;
L.MaxSize = InitSize;
return 0;
}
void IncreaseSize(MyStruct &L,int len)
{
int *p = L.data;
L.data = (int*)malloc((L.MaxSize + len) * sizeof(int));
for (int i = 0; i < L.length; i++)
{
L.data[i] = p[i];
}
L.MaxSize = L.MaxSize + len;
free(p);
}
int main()
{
MyStruct L;
InitList(L);
IncreaseList(L,5);
return 0;
}
顺序表的插入操作:
bool ListInsert(MyStruct &L, int i, int e)
{
if (i<1 || i>L.length+1)
{
cout << "插入位置有误!" << endl;
return false;
}
if (L.length>=L.MaxSize)
{
cout << "目前的顺序表长度大于了最大尺寸" << endl;
return false;
}
for (int j = L.length; j >= i; i--)
{
L.data[j] = L.data[j - 1];
}
L.data[i - 1] = e;
return true;
}
顺序表的删除操作:
bool Listdelete(MyStruct &L, int i, int &e)
{
if (i < 1 || i>=L.length+1)
{
cout << "删除位置不合法" << endl;
return false;
}
if (L.length > L.MaxSize)
{
cout << "数据长度不对" << endl;
return false;
}
cout << "被删除的数据是:" << L.data[i-1] << endl;
e = L.data[i - 1];
for (int j = i-1; j+1 < L.length; j++)
{
L.data[j] = L.data[j + 1];
}
L.length--;
return true;
}
计算一下时间复杂度: 最坏情况:删除表头元素,需要将后续的n-1个元素全部向前移动; 则—————i=1,循环i-1次,最坏的复杂度=O(n); 平均情况:假设 删除任何一个元素的概率相同,即i = 1,2,3…length 的概率是p= 1/n ——————i=1时,循环n-1次;i=2,循环n-2次;i=3 ,循环n-3次。。。。 ——————平均循环次数=(n-1)p+(n-2)p+…+1*p = (n-1)/2 ,时间复杂度O((n-1)/2) ==》O(n)
按位查找 按值查找 (略)
C++ ——— new 、delete 关键字 与C上面的功能相似
- 链表(链表存储)
分类:单链表、双链表、循环链表、静态链表
(1)单链表: 结点互连,一个结点包括(数据空间、存放指向下一个结点的指针) 定义的方法:使用结构体进行定义,在用malloc函数进行单个结点的连续空间的分配
单链表的结点初始化
struct LNode
{
int data;
struct LNode *next_node;
};
struct LNode *p = (struct LNode *)malloc(sizeof(struct LNode));
typedef struct LNode LNode;
LNode *z = (LNode *)malloc(sizeof(LNode));
typedef struct LNode2
{
int data2;
struct LNode2 *next2;
}LNode2,*Linklist;
对于第二种优化: 将struct LNode2 重命名为 LNode2 ,或是将 指针Linklist 指向struct LNode ; 也就是说,LNode2 * L; 等价于 LinkList L; 建议多使用后者,可读性更强,更加强调单链表,前者更加强调结点
创建一个不带头结点链表
typedef struct Listnode
{
int data;
struct Listnode *next;
}Listnode,*Linklist;
bool Init_List(Linklist &L)
{
L = NULL;
return true;
}
bool Linklist_isEmpty(Linklist L)
{
return (L == NULL);
}
void init_linklist()
{
Linklist L;
Init_List(L);
}
创建的一个带结点的单链表(此法写代码更方便)
后面的操作都是基于带结点的单链表 思路: (1)定义一个结点(包括数据与包含下一结点位置的结构体) (2)使用malloc的申请一个结点结构体类型的空间作为头结点 (3)并对头结点的包含下一结点位置的指针元素赋空(NULL)
typedef struct Listnode
{
int data;
struct Listnode *next;
}Listnode,*Linklist;
bool INIT_List(Linklist &L)
{
L = (Listnode *)malloc(sizeof(Listnode));
if (L==NULL)
{
return false;
}
L->next = NULL;
return true;
}
bool Linklist_have_node_inEmpty(Linklist L)
{
return (L->next == NULL);
}
void init_linklist()
{
Linklist L;
INIT_List(L);
}
心中一定要有如下的概念:
单链表的逐个添加新结点
思想上:每次添加时都将结点指针指向最后一个结点,判断依据是最后的结点所保存的位置数据是NULL 时刻记住 :链表的访问特点是不能随机访问,所以每次都要将指针遍历到最后去添加新的结点 &L 是引用指针
bool add_node(Linklist &L, Listnode *n)
{
Linklist list = NULL;
list = L;
while (list->next)
{
list = list->next;
}
n->next = NULL;
list->next = n;
return true;
}
单链表的在中间插入结点——后插
思路: (1)定义一个结点指针P,用循环将它移动到待插位置(比如插3,P指向2)的前一个结点去。 (2)在定义一个新结点S,将你需要加的数据存入到该新结点S (3)先将 S 的 next 的存为 P 旧的 next,然后赋值完后再将 P 的 next 存为 S,此时S就被插进去了。
void input_data(Linklist &L,int i)
{
if (i<1||i>6)
{
cout << "插入操作不合法" << endl;
}
Listnode *p = NULL;
p = L;
for (int j = 0; j < i - 1; j++)
{
p = p->next;
}
Listnode *new_node = new Listnode;
cout << "请输入你要后插入的值:" << endl;
cin >> new_node->data;
cout << " " << endl;
new_node->next = p->next;
p->next = new_node;
}
单链表的在中间插入结点——前插
思路: (1)假设 对结点 a1 进行前插,首先需要使用后插的概念,先插入一个新结点S在 a1 的后面 (2)将 新结点 S 用来存储 a1 的数据,在用原来 旧的 a1 结点作为插入数据的插入结点,即可。 (3)所以此时在对 遍历指针p的调节上,
void input_data_front(Linklist &L, int i,int e)
{
Listnode *S = (Listnode*)malloc(sizeof(Listnode));
Linklist p = NULL;
p = L;
for (int j = 0; j < i; j++)
{
p = p->next;
}
S->next = p->next;
p->next = S;
S->data = p->data;
p->data = e;
}
单链表删除其中的某个结点
思路 (1)首先定义一个结点指针 p,并将 p 指向 头结点 (2)然后 根据 输入的删除位置,移动 指针 p,将其移动到 待删除结点的前一个结点 (3)然后定义新的空结点 q指针,用 q 记录下,删除结点的位置 即 p->next,用 q 获取被删除的值, 再将 p 的 next 赋值为 q 的 next(也就是被删除结点的下一个结点,这样接上了), (4)最后在释放到q,就相当与删除了那个结点
int delete_node(Linklist &L, int i)
{
Linklist p = NULL;
p = L;
int j = 0;
while (p != NULL && j < i - 1)
{
p = p->next;
j++;
}
Listnode *q = NULL;
q = p->next;
int e = q->data;
if (q->next != NULL)
{
p->next = q->next;
}
free(q);
return e;
}
单链表的遍历
思路: (1)先定义一个结点指针p,先指向 表头结点 L 的 next (2)使用 循环 不断迭代p,判断条件就是 此时 p 的next 是否为NULL ,是就说明已经遍历到最后一个了。
void find_data(Linklist &L)
{
Linklist p = NULL;
if (!L->next)
{
cout << "链表为空" << endl;
}
else
{
p = L->next;
while (p)
{
cout << p->data << endl;
p = p->next;
}
}
}
单链表的 尾插法 创建
思路: 创建一个指针 p ,用while去判断输入的值,然后每一轮循环创建一个结点s,s存新输入的值,接上此时的 p 的 next,在将 p 移动到新的 s 上,从而进入新一轮循环
Linklist create_list(Linklist &L)
{
Linklist p = NULL;
p = L;
int e;
cout << "请输入值:" << endl;
cin >> e;
while (e!=-1)
{
Listnode *s = (Listnode*)malloc(sizeof(Listnode));
s->data = e;
p->next = s;
p = s;
cout << "请输入值:" << endl;
cin >> e;
}
p->next = NULL;
return L;
}
单链表的 头插法 创建
思路上: 创建一个指针 p ,一直指向表头结点,每次循环创建一个新结点s,将值插入后,将s接到 L 后面,将p的next 给 s的next (头插法输出的 输入顺序的 倒序)
Linklist create_list_front(Linklist &L)
{
Linklist p = NULL;
p = L;
int e;
cout << "请输入值:" << endl;
cin >> e;
while (e!=-1)
{
Listnode *s = (Listnode*)malloc(sizeof(Listnode));
s->data = e;
s->next = p->next;
L->next = s;
cout << "请输入值:" << endl;
cin >> e;
}
return L;
}
单链表的按位查找 和 按值查找
这个和前面的确定位置插入思路相同
Linklist GetElem(Linklist &L, int i)
{
Linklist p = NULL;
p = L;
int j = 0;
while (p != NULL && j < i )
{
p = p->next;
j++;
}
return p;
}
Linklist GetElem_value(Linklist &L, int e)
{
Linklist p = NULL;
p = L;
while (p != NULL && p->data != e)
{
p = p->next;
}
return p;
}
双链表 及其各种操作
双链表与单链表的区别只是每个结点都新加入了一个保存前驱结点位置为空间,所以编写代码的死路上吗,与单链表大同小异,就不在思路分析了,只给出代码:
namespace x3 {
int length = 0;
typedef struct LNODE
{
int data;
struct LNODE *prior, *next;
}NODE, *doubleList;
bool init_double_list(doubleList &L)
{
L = (NODE*)malloc(sizeof(NODE));
if (L == NULL)
{
return false;
}
L->prior = NULL;
L->next = NULL;
return true;
}
bool double_list_isempty(doubleList &L)
{
if (L->next == NULL)
{
cout << "链表为空" << endl;
return true;
}
return false;
}
doubleList create_double_list(doubleList &L)
{
doubleList p = NULL;
p = L;
int e;
cout << "请输出后插的值:(输入-1结束)" << endl;
cin >> e;
while (e != -1)
{
NODE *s = (NODE*)malloc(sizeof(NODE));
s->data = e;
p->next = s;
s->prior = p;
p = s;
length++;
cin >> e;
}
p->next = NULL;
return L;
}
void find_front(doubleList &L)
{
doubleList p = NULL;
p = L;
while (p->next != NULL)
{
p = p->next;
}
while (p->prior != NULL)
{
cout << p->data << endl;
p = p->prior;
}
}
void find_next(doubleList &L)
{
doubleList p = NULL;
p = L;
while (p->next != NULL)
{
cout << p->next->data << endl;
p = p->next;
}
}
bool input_index(doubleList &L, int e, int i)
{
doubleList p = NULL;
p = L;
if (p->next == NULL)
{
cout << "双链表为空" << endl;
return false;
}
int j = 0;
while (i <= length && j < i - 1)
{
p = p->next;
j++;
}
NODE *s = (NODE*)malloc(sizeof(NODE));
s->data = e;
if (p->next != NULL)
{
s->next = p->next;
p->next->prior = s;
p->next = s;
s->prior = p;
return true;
}
else
{
p->next = s;
s->prior = p;
return true;
}
}
bool inpu_index_front(doubleList &L, int e, int i)
{
doubleList p = NULL;
p = L;
if (p->next == NULL)
{
cout << "双链表为空" << endl;
return false;
}
int j = 0;
while (i <= length && j < i)
{
p = p->next;
j++;
}
NODE *s = (NODE*)malloc(sizeof(NODE));
s->data = e;
s->next = p;
s->prior = p->prior;
p->prior->next = s;
p->prior = s;
return true;
}
}
int main()
{
x3::doubleList LIST = NULL;
x3::init_double_list(LIST);
LIST = x3::create_double_list(LIST);
cout << endl;
x3::find_front(LIST);
cout << endl;
x3::find_next(LIST);
int e, i = 0;
cout << "下面进行插入操作" << endl;
cin >> e;
cout << "位置:" << endl;
cin >> i;
cout << endl;
x3::input_index(LIST, e, i);
x3::find_next(LIST);
cout << endl;
x3::inpu_index_front(LIST, e, i);
x3::find_front(LIST);
return 0;
}
|