单链表与顺序表的问题探究
首先这是顺序表的插入和删除的代码:
//在顺序表L的第i个位置插入元素e
bool ListInsert(SqList &L,int i,ElemType e){
if(i < 1 || i > L.length + 1) //判断i的范围是否有效
return false;
if(L.length >= MaxSize) //当前存储空间已满,不能插入
return false;
for(int j = L.length; j >= i; j--) //将第i个元素及以后元素后移
L.data[j] = L.data[j-1];
L.data[i-1] = e; //在第i个元素放入元素e
L.length++; //顺序表长度加1
return true;
}
//删除顺序表L的第i个元素,并通过引用参数将值返回
bool ListInsert(SqList &L,int i,ElemType &e){
if(i < 1 || i > L.length) //判断i的范围是否有效
return false;
e = L.data[i-1]; //将被删除的元素赋值给e
for(int j = i; j <= L.length - 1; j++) //将第i个位置后的元素前移
L.data[j-1] = L.data[j];
L.length--;
return true;
}
接着是单链表的插入删除代码:
//按序号查找单链表的第i个结点
LNode *GetElem(LinkList L,int i){
int j = 1;//计数,初值为1
LNode *p = L->next; //头结点指针赋值给p
if(i == 0)
return L; //若i等于0,则返回头结点
if(i < 1)
return NULL; //若i无效,则返回NULL
while(p != NULL && j < i){ //从第一个结点开始找,查找第j个结点
p = p -> next;
j++;
}
return p; //返回第i个结点的指针,若i大于表长则返回NULL
}
//按序号插入单链表的第i个结点
bool InsertElem(LinkList L,int i,ElemType e){
LNode *p = GetElem(L,i - 1); //查找插入位置的前驱结点
if(p == NULL) //若该结点为空,则插入失败
return false;
LNode *s = (Lode*)malloc(sizeof(LNode)); //创建新结点s
s->data = e; //将新节点s的数据域赋值为e
s->next = p->next; //新节点s的后继指向p的后继结点
p->next = s; //p结点的后继指向新节点s
return true;
}
//按序号删除单链表的第i个结点
bool DeleteElem(LinkList L,int i,ElemType &e){
LNode *p = GetElem(L,i - 1); //查找删除位置的前驱结点
if(p == NULL) //若该结点为空,则插入失败
return false;
LNode *q = p->next; //q指向被删除结点
p->next = q->next; //将q结点从链中断开
free(q); //释放结点的存储空间
return true;
}
分析这两段代码,我们会发现不论是顺序表的插入删除操作,还是单链表的插入删除操作,时间复杂度均为O(n),空间复杂度均为O(1)。
但我们学过数据结构后,一定听说过这么一句话:“当涉及到大量的插入与删除操作时,用链表比用顺序表效率更高,效果更佳。”
可从时间复杂度和空间复杂度上来看,这两种方式的效果明明几乎是一样的啊…为什么用链表的方式来进行插入与删除操作,效率就会比顺序表要高呢?
这就涉及到计算机组成原理的知识了。。。
虽然顺序表和单链表插入与删除算法的时间复杂度均为O(n),但由于单链表的插入删除操作中,主要时间用于指针后移,寻找插入与删除的位置,在硬件层次上表现为CPU做加法运算…
这个应该不难理解吧,C语言如果学过指针,都知道指针是指向一个内存单元的,那指针的移动,就相当于指针所指向的内存单元的变化,而内存单元的地址标号又是整数。所以从硬件角度上来看,指针的移动就相当于CPU对指针所指向的内存单元地址进行加法或减法运算。
而在顺序表的操作中,主要的时间用于元素移动,元素的移动又是通过赋值操作实现的。因此在硬件层次上需要让CPU从Cache或主存中取数,读数,再进行运算操作,这显然是比用链表实现的速度更慢的…
(如果还有疑问,我相信也许是这个疑问:指针的移动就不需要进行取数操作吗?
我的回答是:首先CPU是一条一条执行指令的,当前欲执行的指令从IR送入CPU后,CPU开始解析,当它解析指针时,可以直接就解析出一个地址,而直接对这个地址进行运算,因而相比顺序表中的赋值操作省去了去存储器内取数的时间)