数组,链表,队列,堆栈!

数组,

一块续的内存空间保存数据,分配内存的时候就是确定的

访问数组中第 个数据的时间花费是 O(1) 但在数组中查找一个指定的数据则是 O(N)

当向数组中插入或者删除数据的时候,最好的情况是在数组的末尾进行操作,时间复杂度是O(1) ,但是最坏情况是插入或者删除第一个数据,时间复杂度是 O(N) 。在数组的任意位置插入或者删除数据的时候,后面的数据全部需要移动,移动的数据还是和数据个数有关所以总体的时间复杂度仍然是 O(N) 

链表

非连续的内存单元,通过指针链接在一起,最后一个节点的指针指向 NULL 不需要提前分配固定大小存储空间需要时候分配一块内存并将这块内存插入链表中

在链表中查找第 个数据以及查找指定的数据的时间复杂度是 O(N) ,但是插入和删除数据的时间复杂度是 O(1) ,只需要调整指针就可以

总之:链表方便插入和删除,时间的复杂度是O(1)

双向链表:

如果希望从表中快速确定某一个结点的前驱,另一个解决方法就是在单链表的每个结点里再增加一个指向其前驱的指针域prior。这样形成的链表中就有两条方向不同的链,我们可称之为双(向)链表(Double Linked List

typedef struct DNode

{

 ElemType data;

 struct DNode *prior,*next;

}DNode,*DoubleList;
包括前驱指针域,数据域,后驱指针域  P->prior->next=p=p->next->prior

1.双向链表的前插操作

int DlinkIns(DoubleList L,int i,ElemType e)

{

 DNode *s,*p;

 …/*先检查待插入的位置i是否合法(实现方法同单链表的前插操作)*/

 …/*若位置i合法,则让指针p指向它*/

 s=(DNode *)malloc(sizeof(DNode));

 if(s)

   {

    s->data=e;      //因为是插入一个元素,将元素内容给s
    s->prior=p->prior;p->prior->next=s;    

    s->next=p;p->prior=s;

    return TRUE;

   }

 else

    teturn fALSE;

}
/*插入位置为i ,插入元素为s,p指针指向i*/

2.双向链表的删除操作

int DlinkDel(DoubleList L,int i,ElemType *e)
{

 DNode *p;

 …/*首先检查待插入的位置i是否合法(实现方法同单链表的删除操作)*/

 …/*若位置i合法,则让指针p指向它*/

 *e=p->prior->next=p->next;

 p->next->prior->p=p->prior;

 free(p);

 return TRUE;

}




循环链表:单向循环链表和单向链表差不多,只不过是最后的尾节点指向的不是空,而是指向头节点

循环链表创建:

#include<iostream>  
using namespace std;  
  
typedef int ElemType;  
  
typedef struct Node  
{  
    ElemType elem;  
    struct Node *next;   
}Node,*linklist;  
  
//创建循环链表  
Node *createList(Node *head,int n)  
{  
    Node *p;  
    for(int i=1;i<=n;i++)  
    {  
        p=(Node*)malloc(sizeof(Node));        //申请内存
        ElemType a;  
        if(!p)  
        {  
            cout<<"内存分配失败"<<endl;  
            exit(0);  
        }  
        cin>>a;  
        p->elem=a;  
        p->next=head->next;  
        head->next=p;  
    }  
    return head;  
}  
  
  
//遍历循环链表  
void printList(Node *head)  
{  
    Node *p;  
    p=head->next;  
    while(p!=head)  
    {  
        cout<<p->elem<<endl;  
        p=p->next;  
    }  
}  
  
void main()  
{  
    Node *head,*p,*q;  
    head=(Node*)malloc(sizeof(Node));  
    if(!head)  
    {  
        cout<<"内存分配失败"<<endl;  
        exit(0);  
    }  
    head->next=head;  
    createList(head,4);  
    printList(head);  
    system("pause");  
}  


JAVA 中有链表么?有什么用?

/*java中的List接口 中有两个实现类:ArrayList和LinkedList。前者是使用数组实现,用索引来取数据是它的优势。后者是用双向链表实现,在插入和删除操作上占优势。具体实现已经封装好了,不用操心过多,具体动作都有具体的方法。*/


队列可以使用数组和链表来实现

双端队列:在两端都可以插入和删除


堆栈

堆 heap

栈stack

/*  C/C++编译的程序占用的内存分为以下几个部分  
  1、栈区(stack)—   由编译器自动分配释放   ,存放函数的参数值,局部变量的值等。其  
  操作方式类似于数据结构中的栈。  
  2、堆区(heap)   —   一般由程序员分配释放,   若程序员不释放,程序结束时可能由OS回  
  收   。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表*/
/*3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的  
  全局变量和静态变量在一块区域,   未初始化的全局变量和未初始化的静态变量在相邻的另  
  一块区域。   -   程序结束后由系统释放。  
  4、文字常量区   —常量字符串就是放在这里的。   程序结束后由系统释放  
  5、程序代码区—存放函数体的二进制代码。*/
/* 二、堆和栈的理论知识    (转载)
  2.1申请方式    
  stack:    
  由系统自动分配。   例如,声明在函数中一个局部变量   int   b;   系统自动在栈中为b开辟空  
  间    
  heap:    
  需要程序员自己申请,并指明大小,在c中malloc函数    
  如p1   =   (char   *)malloc(10);    
  在C++中用new运算符    
  如p2   =   new   char[10];    
  但是注意p1、p2本身是在栈中的。    
   
   
  2.2    
  申请后系统的响应    
  栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢  
  出。    
  堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,  
  会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表  
  中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的  
  首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。  
  另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部  
  分重新放入空闲链表中。    
   
  2.3申请大小的限制    
  栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意  
  思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有  
  的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将  
  提示overflow。因此,能从栈获得的空间较小。    
  堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储  
  的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小  
  受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。    
   
   
   
  2.4申请效率的比较:    
  栈由系统自动分配,速度较快。但程序员是无法控制的。    
  堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.    
  另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是  
  直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。  
     
   
  2.5堆和栈中的存储内容    
  栈:   在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可  
  执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈  
  的,然后是函数中的局部变量。注意静态变量是不入栈的。    
  当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地  
  址,也就是主函数中的下一条指令,程序由该点继续运行。    
  堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。    
   
  2.6存取效率的比较    
   
  char   s1[]   =   "aaaaaaaaaaaaaaa";    
  char   *s2   =   "bbbbbbbbbbbbbbbbb";    
  aaaaaaaaaaa是在运行时刻赋值的;    
  而bbbbbbbbbbb是在编译时就确定的;    
  但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。    
  比如:    
  #include    
  void   main()    
  {    
  char   a   =   1;    
  char   c[]   =   "1234567890";    
  char   *p   ="1234567890";    
  a   =   c[1];    
  a   =   p[1];    
  return;    
  }    
  对应的汇编代码    
  10:   a   =   c[1];    
  00401067   8A   4D   F1   mov   cl,byte   ptr   [ebp-0Fh]    
  0040106A   88   4D   FC   mov   byte   ptr   [ebp-4],cl    
  11:   a   =   p[1];    
  0040106D   8B   55   EC   mov   edx,dword   ptr   [ebp-14h]    
  00401070   8A   42   01   mov   al,byte   ptr   [edx+1]    
  00401073   88   45   FC   mov   byte   ptr   [ebp-4],al    
  第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到  
  edx中,再根据edx读取字符,显然慢了。    
   
   
  2.7小结:    
  堆和栈的区别可以用如下的比喻来看出:    
  使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就  
  走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自  
  由度小。    
  使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由  
  度大。   (经典!)  

全局变量 与 静态变量的 区别




猜你喜欢

转载自blog.csdn.net/pmdream/article/details/79053261