热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

线性表顺序存储和链式存储插入删除操作的效率比较

今晚在研究线性表的两种实现方法插入删除操作的效率问题,一种是利用数组实现,这种方法如果在头部插入,需要把整个链表后移一位,然后再写入数据,同样的,如果采用链式存储结构实现,那么在尾部插入删除时,需要从
今晚在研究线性表的两种实现方法插入删除操作的效率问题,一种是利用数组实现,这种方法如果在头部插入,需要把整个链表后移一位,然后再写入数据,同样的,如果采用链式存储结构实现,那么在尾部插入删除时,需要从head头指针遍历到最后一个然后在进行操作。理论上来说,如果对数组实现进行头部插入删除和链表实现尾部插入删除执行相同规模,链表应当比数组快才对。
可我晚上测试的时候却发现结果相反,数组执行10w次只要13s,链表执行5w次就要100+s,不清楚是我的实现问题还是本就如此,希望你能告诉我具体原因。
下附部分代码和运行截图(如果有需要,我可以继续传其他的代码)。

//Linked_List
template
Error_code List::remove(int position, List_entry & x)
{
if (position<0 || position >= count)return range_out;
if (position == 0) {
x = head->entry;
Node *p=head;
head = head->next;
delete p;
count--;
return success;
}
Node *pre, *cur;
pre = set_position(position-1);
cur = pre->next;
pre->next = cur->next;
x = cur->entry;
delete cur;
count--;
return success;
}

template
Error_code List::insert(int position, const List_entry & x)
{
if (position<0 || position > count)return range_out;
if (full())return overflow;
if (position == 0) {
head = new Node(x, head);
count++;
return success;
}
Node *pre = set_position(position - 1);
Node *cur = pre->next;
pre->next = new Node(x,cur);
count++;
return success;
}

#include"Linked_List.cpp"
#include
#include
using namespace std;
void main() {
List  test;
clock_t start_time = clock();
{
for (int i = 0; i <100000; i++) { //初始化
test.insert(i, i);
}
}
clock_t end_time = clock();
cout << "Linked_List Tail Insert Running time is: " << static_cast(end_time - start_time) / CLOCKS_PER_SEC * 1000 << "ms" << endl;//输出运行时间

start_time = clock();
{
//被测试代码
for (int i = 99999; i >=0; i--) {
int x;
test.remove(i, x);
}
}
end_time = clock();
cout << "Linked_List Tail Delete Running time is: " << static_cast(end_time - start_time) / CLOCKS_PER_SEC * 1000 << "ms" << endl;//输出运行时间
}


//Squence_List
template
Error_code List::remove(int position, List_entry & x)
{
if (empty())return underflow;
if (position<0 || position>=count)return range_out;
for (int i =position; i < count-1; i++) {
entries[i] = entries[i + 1];
}
count--;
return success;
}

template
Error_code List::insert(int position, const List_entry & x)
{
if (full())return overflow;
if (position<0 || position>count) return range_out;
for (int i = count; i > position; i--) {
entries[i] = entries[i - 1];
}
entries[position] = x;
count++;
return success;
}

#include"Sequence_Link.cpp"
#include
#include
#include
using namespace std;
void main() {
List  test;
clock_t start_time = clock();
{
//被测试代码
for (int i = 0; i < 100000; i++) {
test.insert(0, 99999);
}
}
clock_t end_time = clock();
cout << "Sequence_List Tail Insert Running time is: " << static_cast(end_time - start_time) / CLOCKS_PER_SEC * 1000 << "ms" << endl;//输出运行时间

start_time = clock();
{
//被测试代码
for (int i = 99999; i >=0; i--) {
int x;
test.remove(0, x);
}
}
end_time = clock();
cout << "Sequence_List Tail Delete Running time is: " << static_cast(end_time - start_time) / CLOCKS_PER_SEC * 1000 << "ms" << endl;//输出运行时间
}

9 个解决方案

#1


“如果采用链式存储结构实现,那么在尾部插入删除时, 需要从head头指针遍历到最后一个然后在进行操作”.....
不会储存一个尾指针么, std::list::back的效率不是低的吓死人.
链表不是随机储存的容器,支持随机位置插入/删除反而画蛇添足.

而lz问题的答案....没仔细看代码,目测是CPU的缓存引起的

#2


数组查询快,链表增删快
在不知道索引的情况下,你要查找数据,遍历是必须的。
链表同样需要遍历,而且代价更高。
你要取到中间的某个值,必须从某一个节点开始往一个方向搜索,因为链表不是连续存储,所以这个消耗是比较大的。

提高C++程序运行效率的10个简单方法:http://www.jb51.net/article/54792.htm

#3


楼主说的是数据结构的理论效率,现在 cpu 缓存的情况下,绝大多数的时候都是 vector 快,除非元素数目特别巨大,否则 list 没有优势。

#4


引用 2 楼 qq423399099 的回复:
数组查询快,链表增删快
在不知道索引的情况下,你要查找数据,遍历是必须的。
链表同样需要遍历,而且代价更高。
你要取到中间的某个值,必须从某一个节点开始往一个方向搜索,因为链表不是连续存储,所以这个消耗是比较大的。

提高C++程序运行效率的10个简单方法:http://www.jb51.net/article/54792.htm

我在测试的过程中是进行了10w次的增删操作,按道理来说链表快才对,因为数组要将所有的数据后移一位而链表只要从头指针遍历到最后就可以(注:我这里是对数组头部插入,对链表尾部插入,都考虑他们理论上最坏的情况);讨论的前提是不对链表结构做出改变或者说优化。结果却让我很疑惑。

#5


引用 1 楼 dustpg 的回复:
“如果采用链式存储结构实现,那么在尾部插入删除时, 需要从head头指针遍历到最后一个然后在进行操作”.....
不会储存一个尾指针么, std::list::back的效率不是低的吓死人.
链表不是随机储存的容器,支持随机位置插入/删除反而画蛇添足.

而lz问题的答案....没仔细看代码,目测是CPU的缓存引起的

当然,优化这种结构就是另一个问题了;现在如果在不改变这两种结构的同时对他们进行最差情况的测试,增删操作应该是链表快不是吗?

#6


"增删操作应该是链表快不是吗?"
的确,但是lz的代码比较的是“增删操作”吗,我怎么觉得是进行的遍历效率的检查,请看看std::list的删除操作的参数.
我的“画蛇添足”说法已经非常清楚了.

#7


那我也附上一段代码,先上结果:

可见相差3个数量级.
这是剖析结果(CPU占用总览):


随机容器std::vector的CPU消耗在:

std::vector:;emplace有80%+

双链表容器std::list的CPU消耗在:

7层花在购买节点(申请空间)
3层花在释放空间上.
没有多余消耗.

下面是测试代码:
#include 
#include 
#include 
#include 

// test vector
void test_vector(int count) {
    std::vector vector;
    std::cout << "vector testing..." << std::endl;
    {
        auto t1 = std::chrono::high_resolution_clock::now();
        for (int i = 0; i < count; ++i) {
            vector.insert(vector.begin(), 99999);
        }
        auto t2 = std::chrono::high_resolution_clock::now();
        std::cout << "vector insert took: "
            << std::chrono::duration_cast(t2 - t1).count()
            << "ms"
            << std::endl;
    }
    {
        auto t1 = std::chrono::high_resolution_clock::now();
        for (int i = 0; i < count; ++i) {
            vector.erase(vector.begin());
        }
        auto t2 = std::chrono::high_resolution_clock::now();
        std::cout << "vector remove took: "
            << std::chrono::duration_cast(t2 - t1).count()
            << "ms"
            << std::endl;
    }
}

// test list
void test_list(int count) {
    std::list list;
    std::cout << "list testing..." << std::endl;
    {
        auto t1 = std::chrono::high_resolution_clock::now();
        for (int i = 0; i < count; ++i) {
            list.insert(list.end(), 99999);
        }
        auto t2 = std::chrono::high_resolution_clock::now();
        std::cout << "list insert took: "
            << std::chrono::duration_cast(t2 - t1).count()
            << "ms"
            << std::endl;
    }
    {
        auto t1 = std::chrono::high_resolution_clock::now();
        for (int i = 0; i < count; ++i) {
            list.erase(--list.end());
        }
        auto t2 = std::chrono::high_resolution_clock::now();
        std::cout << "list remove took: "
            << std::chrono::duration_cast(t2 - t1).count()
            << "ms"
            << std::endl;
    }
}

// entry
auto main() noexcept -> int {
    std::cout << "enter to test vector" << std::endl;
    std::getchar();
    test_vector(100000);
    std::cout << "enter to test list" << std::endl;
    std::getchar();
    test_list(100000);
    std::getchar();
    return EXIT_SUCCESS;
}

#8


无profiler不要谈效率!!尤其在这个云计算、虚拟机、模拟器、CUDA、多核 、多级cache、指令流水线、多种存储介质、……满天飞的时代!

#9


引用 7 楼 dustpg 的回复:
那我也附上一段代码,先上结果:

可见相差3个数量级.
这是剖析结果(CPU占用总览):


随机容器std::vector的CPU消耗在:

std::vector:;emplace有80%+

双链表容器std::list的CPU消耗在:

7层花在购买节点(申请空间)
3层花在释放空间上.
没有多余消耗.

下面是测试代码:
#include 
#include 
#include 
#include 

// test vector
void test_vector(int count) {
    std::vector vector;
    std::cout << "vector testing..." << std::endl;
    {
        auto t1 = std::chrono::high_resolution_clock::now();
        for (int i = 0; i < count; ++i) {
            vector.insert(vector.begin(), 99999);
        }
        auto t2 = std::chrono::high_resolution_clock::now();
        std::cout << "vector insert took: "
            << std::chrono::duration_cast(t2 - t1).count()
            << "ms"
            << std::endl;
    }
    {
        auto t1 = std::chrono::high_resolution_clock::now();
        for (int i = 0; i < count; ++i) {
            vector.erase(vector.begin());
        }
        auto t2 = std::chrono::high_resolution_clock::now();
        std::cout << "vector remove took: "
            << std::chrono::duration_cast(t2 - t1).count()
            << "ms"
            << std::endl;
    }
}

// test list
void test_list(int count) {
    std::list list;
    std::cout << "list testing..." << std::endl;
    {
        auto t1 = std::chrono::high_resolution_clock::now();
        for (int i = 0; i < count; ++i) {
            list.insert(list.end(), 99999);
        }
        auto t2 = std::chrono::high_resolution_clock::now();
        std::cout << "list insert took: "
            << std::chrono::duration_cast(t2 - t1).count()
            << "ms"
            << std::endl;
    }
    {
        auto t1 = std::chrono::high_resolution_clock::now();
        for (int i = 0; i < count; ++i) {
            list.erase(--list.end());
        }
        auto t2 = std::chrono::high_resolution_clock::now();
        std::cout << "list remove took: "
            << std::chrono::duration_cast(t2 - t1).count()
            << "ms"
            << std::endl;
    }
}

// entry
auto main() noexcept -> int {
    std::cout << "enter to test vector" << std::endl;
    std::getchar();
    test_vector(100000);
    std::cout << "enter to test list" << std::endl;
    std::getchar();
    test_list(100000);
    std::getchar();
    return EXIT_SUCCESS;
}

多谢,我从一开始的测试的对象就错了,受教了。

推荐阅读
  • C++实现经典排序算法
    本文详细介绍了七种经典的排序算法及其性能分析。每种算法的平均、最坏和最好情况的时间复杂度、辅助空间需求以及稳定性都被列出,帮助读者全面了解这些排序方法的特点。 ... [详细]
  • 本文基于刘洪波老师的《英文词根词缀精讲》,深入探讨了多个重要词根词缀的起源及其相关词汇,帮助读者更好地理解和记忆英语单词。 ... [详细]
  • 1.如何在运行状态查看源代码?查看函数的源代码,我们通常会使用IDE来完成。比如在PyCharm中,你可以Ctrl+鼠标点击进入函数的源代码。那如果没有IDE呢?当我们想使用一个函 ... [详细]
  • 本文详细介绍了Java中org.w3c.dom.Text类的splitText()方法,通过多个代码示例展示了其实际应用。该方法用于将文本节点在指定位置拆分为两个节点,并保持在文档树中。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 本文详细探讨了KMP算法中next数组的构建及其应用,重点分析了未改良和改良后的next数组在字符串匹配中的作用。通过具体实例和代码实现,帮助读者更好地理解KMP算法的核心原理。 ... [详细]
  • 本文详细介绍了Java中org.neo4j.helpers.collection.Iterators.single()方法的功能、使用场景及代码示例,帮助开发者更好地理解和应用该方法。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本题探讨了一种字符串变换方法,旨在判断两个给定的字符串是否可以通过特定的字母替换和位置交换操作相互转换。核心在于找到这些变换中的不变量,从而确定转换的可能性。 ... [详细]
  • 本文深入探讨 MyBatis 中动态 SQL 的使用方法,包括 if/where、trim 自定义字符串截取规则、choose 分支选择、封装查询和修改条件的 where/set 标签、批量处理的 foreach 标签以及内置参数和 bind 的用法。 ... [详细]
  • 本文介绍如何使用 Python 编写程序,检查给定列表中的元素是否形成交替峰值模式。我们将探讨两种不同的方法来实现这一目标,并提供详细的代码示例。 ... [详细]
  • XNA 3.0 游戏编程:从 XML 文件加载数据
    本文介绍如何在 XNA 3.0 游戏项目中从 XML 文件加载数据。我们将探讨如何将 XML 数据序列化为二进制文件,并通过内容管道加载到游戏中。此外,还会涉及自定义类型读取器和写入器的实现。 ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 本文介绍如何使用 NSTimer 实现倒计时功能,详细讲解了初始化方法、参数配置以及具体实现步骤。通过示例代码展示如何创建和管理定时器,确保在指定时间间隔内执行特定任务。 ... [详细]
  • 本文介绍如何通过创建替代插入触发器,使对视图的插入操作能够正确更新相关的基本表。涉及的表包括:飞机(Aircraft)、员工(Employee)和认证(Certification)。 ... [详细]
author-avatar
sdx3418153
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有