数据结构
1. 栈和堆
- 定义
- 栈(stack)又名堆栈,是一种运算受限的线性表,其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素
- 优缺点
- 存取速度比堆要快,仅次于直接位于CPU中的寄存器
- 但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据在多个线程或者多个栈之间是不可以共享的,但是在栈内部多个值相等的变量是可以指向一个地址的,
-
栈和堆的区别理解
- 栈和堆是什么时候创建的 ---当线程创建时,os为每一个系统级的线程分配栈,通常情况下操作系统通过调用语言的运行时为应用创建堆
- 分别的作用范围: 栈附属与线程,因此当线程结束栈被回收,堆通常是在应用程序启动的时候被分配,应用程序退出的时候被回收
- 栈和堆的大小由什么决定:线程创建的时候设置栈的大小,在应用程序启动的时候设置堆的大小,但是在需要的时候扩展,分配器向内存申请
- 栈和堆的速度比较: 栈比堆要快,因为她的存取模式使她轻松的分配和重新分配内存(指针/整形只是进行简单的递增或者递减运算),然而堆在分配和释放的时候由更多复杂的bookkeeping参与,另外,在栈上的每个字节频繁的被服用也就意味着它可能映射到处理器缓存中,所以很快;
- 较为经典的理解http://blog.jobbole.com/75321/
-
数组/链表/队列/栈
- 数组 是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。
- 链表 链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起,每个结点包括两个部分:一个是存储 数据元素的数据域,另一个是存储下一个结点地址的 指针。
- 队列 队列是一种操作受限制的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作
- 栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底;
- 栈和队列的区别
- 栈是限定只能在表的一端进行插入和删除操作的线性表。 队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表
2.数据结构(Data Structure)是指互相之间存在着一种或多种关系的数据元素的集合。在任何问题中,数据元素之间都不会是孤立的,在它们之间都存在着这样或那样的关系,这种数据元素之间的关系称为结构。根据数据元素间关系的不同特性,通常有下列四类基本的结构:
- 集合结构。在集合结构中,数据元素间的关系是“属于同一个集合”。集合是元素 关系极为松散的一种结构。
- 线性结构。该结构的数据元素之间存在着一对一的关系。
- 树型结构。该结构的数据元素之间存在着一对多的关系。
- 图形结构。该结构的数据元素之间存在着多对多的关系,图形结构也称作网状结构。
算法的时间复杂度和空间复杂度
- 时间复杂度
-
时间频度
一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为T(n);
-
时间复杂度
在刚才提到的时间频度中,n称为问题的规模,当n不断变化时,时间频度T(n)也会不断变化。但有时我们想知道它变化时呈现什么规律。为此,我们引入时间复杂度概念。 一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。
T (n) = Ο(f (n)) 表示存在一个常数C,使得在当n趋于正无穷时总有 T (n) ≤ C * f(n)。简单来说,就是T(n)在n趋于正无穷时最大也就跟f(n)差不多大。也就是说当n趋于正无穷时T (n)的上界是C * f(n)。其虽然对f(n)没有规定,但是一般都是取尽可能简单的函数。例如,O(2n2+n +1) = O (3n2+n+3) = O (7n2 + n) = O ( n2 ) ,一般都只用O(n2)表示就可以了。注意到大O符号里隐藏着一个常数C,所以f(n)里一般不加系数
从图中可见,我们应该尽可能选用多项式阶O(nk)的算法,而不希望用指数阶的算法。常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)<…<Ο(2n)<Ο(n!)一般情况下,对一个问题(或一类算法)只需选择一种基本操作来讨论算法的时间复杂度即可,有时也需要同时考虑几种基本操作,甚至可以对不同的操作赋予不同的权值,以反映执行不同操作所需的相对时间,这种做法便于综合比较解决同一问题的两种完全不同的算法。
-
计算
将基本语句执行次数的数量级放入大Ο记号中。 如果算法中包含嵌套的循环,则基本语句通常是最内层的循环体,如果算法中包含并列的循环,则将并列循环的时间复杂度相加。例如 for (i=1; i<=n; i++) x++; for (i=1; i<=n; i++) for (j=1; j<=n; j++) x++; 第一个for循环的时间复杂度为Ο(n),第二个for循环的时间复杂度为Ο(n2),则整个算法的时间复杂度为Ο(n+n2)=Ο(n2)。 Ο(1)表示基本语句的执行次数是一个常数,一般来说,只要算法中不存在循环语句,其时间复杂度就是Ο(1)。其中Ο(log2n)、Ο(n)、 Ο(nlog2n)、Ο(n2)和Ο(n3)称为多项式时间,而Ο (2n)和Ο(n!)称为指数时间。
-
理解
- 一个经验规则:其中c是一个常量,如果一个算法的复杂度为c 、 log2n 、n 、 n*log2n ,那么这个算法时间效率比较高 ,如果是2n ,3n ,n!,那么稍微大一些的n就会令这个算法不能动了,居于中间的几个则差强人意。
-
时间复杂度评价性能
有两个算法A1和A2求解同一问题,时间复杂度分别是T1(n)=100n2,T2(n)=5n3。(1)当输入量n<20时,有T1(n)>T2(n),后者花费的时间较少。(2)随着问题规模n的增大,两个算法的时间开销之比5n3/100n2=n/20亦随着增大。即当问题规模较大时,算法A1比算法A2要有效地多。它们的渐近时间复杂度O(n2)和O(n3)从宏观上评价了这两个算法在时间方面的质量。在算法分析时,往往对算法的时间复杂度和渐近时间复杂度不予区分,而经常是将渐近时间复杂度T(n)=O(f(n))简称为时间复杂度,其中的f(n)一般是算法中频度最大的语句频度。
-
-
2. 算法的空间复杂度
1. 一个算法的空间复杂度(Space Complexity)S(n)定义为该算法所耗费的存储空间,它也是问题规模n的函数。渐近空间复杂度也常常简称为空间复杂度。
2. 分为三部分
- 固定部分。这部分空间的大小与输入/输出的数据的个数多少、数值无关。主要包括指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间。这部分属于静态空间。
- 可变空间,这部分空间的主要包括动态分配的空间,以及递归栈所需的空间等。这部分的空间大小与算法有关。一个算法所需的存储空间用f(n)表示。S(n)=O(f(n)) 其中n为问题的规模,S(n)表示空间复杂度
代码
单链表的实现
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
//定义链表的节点,由当前的节点值外加指向下个节点的指针
//NODE等价于struct Node, PNODE等价于struct Node *
typedef struct Node{
int data;
struct Node *pNext;
}NODE,*PNODE;
//函数的声明
PNODE createLinkList(); //定义创建链表的指针函数
void traverseLinkList(PNODE pHead);//遍历链表的函数
bool isEmpty(PNODE pHead) ;//判断链表是否为空的函数
int getLength(PNODE pHead);//获取链表的长度
bool insertElement(PNODE pHead, int pos, int val); //向链表中插入元素的函数,三个参数依次为链表头结点、要插入元素的位置和要插入元素的值
bool deleteElement(PNODE pHead, int pos, int * pVal); //从链表中删除元素的函数,三个参数依次为链表头结点、要删除的元素的位置和删除的元素的值
void sort(PNODE pHead); //对链表中的元素进行排序的函数(基于冒泡排序)
int main(void){
int val;
PNODE pHead = NULL;
pHead = createLinkList();
traverseLinkList(pHead);
if(isEmpty(pHead)){
printf("链表为空!\n");
}else{
printf("链表不为空!\n");
}
printf("链表的长度为:%d\n",getLength(pHead));
sort(pHead);
traverseLinkList(pHead);
if(deleteElement(pHead,3,&val))
printf("删除元素成功!删除的元素是:%d\n",val);
else
printf("删除元素失败!\n");
traverseLinkList(pHead);
system("pause");
return 0 ;
}
//创建一个链表
PNODE createLinkList(void){
int length;
int value;
int i;
//创建一个不存放有效数据的头节点
PNODE pHead = (PNODE)malloc(sizeof(NODE));
if(pHead==NULL){
printf("内存分配失败,程序退出!\n");
exit(-1);
}
PNODE pTail = pHead;
pTail->pNext=NULL;
printf("请输入您想要创建链表节点的个数: len = ");
scanf("%d\n",&length);
for( i = 0 ; i < length ; i ++){
printf("请输入第%d个节点的值:",i+1);
scanf("%d",&value);
PNODE pNew= (PNODE)malloc(sizeof(NODE));
if(pNew==NULL){
printf("内存分配失败,程序退出!\n");
exit(-1);
}
pNew->data=value;//新节点中放入值
pTail->pNext=pNew;//将尾节点指向新的节点
pNew->pNext=NULL;//将新的节点的指针域清空
pTail=pNew;//指针往后移动,将新的节点赋值给pTail,使得pTail始终指向尾节点
}
return pHead;
}
//遍历链表
void traverseLinkList(PNODE pHead) {
PNODE p = pHead->pNext;
while(NULL != p)
{
printf("%d ", p->data);
p = p->pNext;
}
printf("\n");
return;
}
bool isEmpty(PNODE pHead)
{
if(NULL == pHead->pNext)
return true;
else
return false;
}
int getLength(PNODE pHead)
{
PNODE p = pHead->pNext; //指向首节点
int len = 0; //记录链表长度的变量
while(NULL != p)
{
len++;
p = p->pNext; //p指向下一结点
}
return len;
}
void sort(PNODE pHead) {
int len = getLength(pHead); //获取链表长度
int i, j, t; //用于交换元素值的中间变量
PNODE p, q; //用于比较的两个中间指针变量
for(i=0,p=pHead->pNext ; i<len-1 ; i++,p=p->pNext){
for(j=i+1,q=p->pNext;j<len;j++,q=q->pNext){
if(p->data > q->data){
t = p->data;
p->data = q->data;
q->data = t;
}
}
}
return;
}
bool insertElement(PNODE pHead, int pos, int val){
int i = 0;
PNODE p = pHead;
//判断p是否为空并且使p最终指向pos位置的结点
while(NULL!=p && pos-1>i){
p = p->pNext;
i++;
}
if(NULL==p || i>pos-1)
return false;
//创建一个新结点
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if(NULL == pNew){
printf("内存分配失败,程序退出!\n");
exit(-1);
}
pNew->data = val;
//定义一个临时结点,指向当前p的下一结点
PNODE q = p->pNext;
//将p指向新结点
p->pNext = pNew;
//将q指向之前p指向的结点
pNew->pNext = q;
return true;
}
bool deleteElement(PNODE pHead, int pos, int * pVal){
int i = 0;
PNODE p = pHead;
//判断p是否为空并且使p最终指向pos结点
while(NULL!=p->pNext && i<pos-1){
p = p->pNext;
i++;
}
if(NULL==p->pNext || i>pos-1)
return false;
//保存要删除的结点
* pVal = p->pNext->data;
//删除p后面的结点
PNODE q = p->pNext;
p->pNext = p->pNext->pNext;
free(q);
q = NULL;
return true;
}