目录
第一回合:数据结构基本概念
第二回合:算法基本概念
第三回合:线性表顺序存储结构
第四回合:线性表链式存储结构
第五回合:静态链表和循环链表
第六回合:栈
第七回合:队列
第八回合:串
数据结构基本概念
数据结构:数据间相互存在一种或者多种特定关系的数据元素的集合
数据:数据是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合
数据元素:数据元素是是组成数据的、有一定意义的基本单位,在计算机中通常作为整体处理。
数据项:一个数据元素可以由若干个数据项组成。数据项是数据不可分割的最小单位。
数据对象:数据对象是性质相同的数据元素的集合,是数据的子集。
逻辑结构:逻辑结构是指数据对象中数据元素之间的相互关系(人为的),也就是说逻辑结构是程序员按照一定逻辑来处理数据。
集合结构:集合结构中的数据元素除了同属于一个集合外,它们之间没有其他关系。
线性结构:线性结构中的数据元素之间是一对一的关系。
树形结构:树形结构中的数据元素之间存在一种一对多的层次关系。
图形结构:图形结构的数据元素是多对多的关系。
物理结构:是指数据的逻辑结构在计算机中的存储形式
顺序存储结构:顺序存储结构是把数据元素存放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的。
链式存储结构:是把数据元素存放在任意存储单元里,这组存储单元可以是连续的也可以不是连续的。
数据类型:数据类型是指性质相同的值的集合以及定义在次集合上的一些操作的总称。
抽象数据类型:抽象数据类型是指一个数学模型以及定义在该模型上的一组操作,抽象是指抽取出事物具有的普遍性的本质。(例:有三十个人:十个会打篮球,十个会唱歌,十个会跳舞,我们将其组成篮球队,音乐队和舞蹈队,组队的过程即为抽象!(个人理解))
算法基本概念
算法:算法是解决特定问题求解步骤描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或者多个操作。
算法的特性:算法具有五个基本特征:输入、输出、有穷性、确定性、可行性。
输入输出:算法具有零个或多个输入,算法至少有一个或多个输出。
有穷性:有穷性是指在算法执行有限步骤之后,自动结束而不会出现无限循环,并且每个步骤在可接受的时间内完成。
确定性:算法的每一步骤都有具体含义,不会出现二义性。
可行性:算法的每一步都必须是可行的,也就是说,每一步都能够通过执行有限次数完成。
算法的设计要求:算法设计要求主要有以下几点
正确性:算法的正确性是指算法至少应该具有输入、输出和加工处理无歧义性、能正确反映问题需求、能够得到问题的答案。
可读性:算法设计的另一个目的是为了便于阅读、理解和交流。
健壮性:当输入数据不合法时,算法也能作出相关处理,而不是产生异常或者程序卡死。
时间效率高和存储量低:设计算法应该尽量满足时间效率高和存储量低的需求。
算法效率度量方法:事前分析估算方法和事后统计法
事后统计方法:这种方法主要是通过设计好测试程序和数据,利用计算机计时器对不同的算法编制的程序的运行时间进行比较,从而确定算法效率高低。
事前分析估算方法:在计算机程序编制前,依据统计方法对算法进行估算。
算法时间复杂度:在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间度量,记作:T(n) = O(f(n))。它表示随着问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,其中f(n)是关于问题规模n的某个函数。这样用O()来体现算法时间复杂度的记法称为大O记法。
推倒大O阶:用常数1取代运行时间中所有加法常数;在修改后的运行次数函数中,只保留最高阶项;如果最高阶项存在且不是1,则去除与这个项相乘的常数,得到的结果就是大O阶;分析算法的复杂度,关键就是要分析循环结构的运行情况。
线性表顺序存储结构
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
int array[20];
int length;
}List;
/*创建线性表顺序存储结构*/
List CreateList()
{
int i = 0;
List list = {0};
for(;i<20;i++)
{
int data = i+1;
list.array[i] = data;
list.length++;
}
return list;
}
/*获取顺序表中指定下标的元素*/
int GetDataInList(List list,int index)
{
return list.array[index];
}
/*
指定位置插入一条数据
*/
int InsertDataInList(List* list,int data,int index)
{
int k = index;
if((*list).length>=20||index<0)
{
return 0;
}
/*插入的数据不在表头或者表尾*/
if(index!=0&&index!=list->length-1)
{
for(;k<list->length;k++)
{
//从插入处向后移动数据
int temp = (*list).array[k+1];
(*list).array[k+1] = (*list).array[k];
(*list).array[k] = temp;
}
}
(*list).array[index] = data;
(*list).length++;
return 1;
}
/*删除数据*/
int RemoveData(List* list,int index)
{
int k = index;
if((*list).length<=0||index<0)
{
return 0;
}
if(index!=0&&index!=list->length-1)
{
for(;k<list->length;k++)
{
//从删除处向后移动数据
(*list).array[k] = (*list).array[k+1];
}
(*list).length--;
}
return 1;
}
线性表顺序存储结构
由于链表结构比较重要 ,在很多的数据结构中都是由链表构成或者操作的所以链表结构采用C、C#两种语言实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
实现代码之前简单对单链表架构和顺序存储结构做对比
从存储分配方式上看,顺序存储结构用一段连续的存储单元
依次存储线性表的数据元素而单链表采用链式存储结构,用一组
任意的存储单元存放线性表的元素
从时间性能能上分析,顺序表方便查找 单链表方便插入和删除
从空间性能上分析,顺序存储结构需要预分配存储空间,分大了浪费
分小了容易发生溢出,而单链表不需要分配存储空间,只要有就可以分配
元素个数也不受限制,下面我们先用C语言简单实现一遍
*/
typedef struct Node
{
int data; // 数据域
struct Node *next; // 指针域
}Node;
typedef struct Node *LinkList; // 定义指针
//获取链表中第i个元素并用指针e返回
void GetElem(LinkList L,int i,int *e)
{
int j;
LinkList p;
p = L->next;
j = 1;
while(p&&j<i)
{
p = p->next;
++j;
}
*e = p->data;
}
/*
两种方式感受一下编程思想,就是你想怎么做就怎么做。。。
*/
//返回单链表中第i个数据
int RetElem(LinkList L,int i)
{
LinkList p = L->next;
int j = 1;
while(P&&j<i)
{
p = p->next;
j++;
}
return p->data;
}
//在链表中插入数据i表示插入的位置
void ListInsert(LinkList *L,int i,int e)
{
/*
这里为什么使用二级指针呢?? 因为我们现在要做的插入操作
要对我们传入的链表进行修改, 但是如果我们传入Node* 也就是
LinkList 本质上传入一个指针 也就是函数的实参和形参指向的
是内存中的一块地址 这样的话 如果我们 使用 L->next = (int*)malloc(sizeof(int))
的话 实际上是形参指针指针改变了指向 并没有修改实参本身的值
而二级指针指向的是一级指针的地址,这样的话如果我们使用 malloc函数的话
实际上改变的就是一级指针地址的内容 也就是我们的单链表自身的地址
*/
LinkList p = *L;
LinkList k;
int j = 1;
while(p&&j<i)
{
p = p->next;
j++;
}
k = (LinkList)malloc(sizeof(Node));
k->data = e;
k->next = p->next;
p->next = k;
}
//删除单链表中第i个位置的数据 用e返回删除的数据
void ListDelete(LinkList *L,int i,int *e)
{
LinkList p , q;
p = *L;
int j = 0;
while(p&&j<i)
{
p = p->next;
j++;
}
q = p->next;
p->next = q->next;
free(p);
}
/*
单链表正表创建,单链表和顺序存储结构不一样,它不像顺序存储结构那么集中
单链表是动态结构在形式上是很散的,对于每个链表来说,它所占用的空间
和大小和位置是不需要预先分配划定的,可以根据实际情况即时生成的
*/
//随机产生n个元素值,建立带表头结点的单链表L(使用头插法)
void CreateListHead(LinkList *L,int n)
{
LinkList p;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL;
for(i = 0;i<n;i++)
{
p = (LinkList)malloc(sizeof(Node));
p->data = rand()%100+1;
p->next = (*L)->next;
(*L)->next = p;
}
}
/*随机产生N个元素的值,建立带表头结点的单链表线性表L(尾插法)*/
void CreateListTail(LinkList *L,int n)
{
LinkList p,r;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
r= *L;
for(i = 0;i<n;i++)
{
p = (Node*)malloc(sizeof(Node));
p->data = rand()%100+1;
r->next = p;
r = p;
}
r->next = null;
}
//单链表整表删除
void ClearList(LinkList *L)
{
LinkList p,q;
p = (*L)->next;
while(p)
{
q = p-<next;
free(p)
p = q;
}
(*L)->next = NULL;
}