C++(数据结构与算法):08---线性表的实现(数组形式)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_41453285/article/details/103198828
            </div>
                                                <!--一个博主专栏付费入口-->
         
         <!--一个博主专栏付费入口结束-->
        <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-4a3473df85.css">
                                    <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-4a3473df85.css">
            <div class="htmledit_views" id="content_views">
                                        <h1><a name="t0"></a><span style="color:#f33b45;">一、线性表的介绍</span></h1>

二、数组实现线性表的方法

方式一

  • 从数组的头端实现
  • 例如有一个数组element[10],我们可以将元素{5,2,4,8,1}从其索引0处作为线性表的头开始实现,线性表的方向从左至右

方式二

  • 从数组的尾端实现
  • 接方式一,我们将数组element的最后一个索引处作为线性表的头开始实现,线性表的方向从右至左

方式三

  • 从数组的指定索引处实现,我们可以从数组的指定索引处作为线性 表的头部开始实现
  • 公式为:localtion(i)=(localtion(0)+i)%arrayLength
    • localtion(0)是选取的线性表在数组中的第一个位置
    • arrayLength是数组的长度
  • 例如,下面我们将数组element的索引7作为线性表的头部开始从左至右实现,当元素超出数组的长度时,将元素从数组的索引0处开始插入。此处我们的公式为,localtion(i)=(7+i)%10

三、编码实现

异常类实现(illegalParameterValue)

  • 这个异常类主要用来处理函数的参数异常

 
 
  1. class illegalParameterValue
  2. {
  3. private:
  4. string message;
  5. public:
  6. illegalParameterValue() : message( "Illegal Parameter Value") {}
  7. illegalParameterValue( const char* s) :message(s) {}
  8. const char *what() {
  9. return message.c_str();
  10. }
  11. };

修改数组长度函数(changeLength)

  • 功能:该函数用来改变原来数组的长度,在函数中我们创建一个新的数组,然后将原数组拷贝进新数组,然后释放原数组所指向的地址空间(这个函数是全局函数,并不属于某一类)
  • 参数:
    • 参数1:数组指针引用
    • 参数2:原数组的长度
    • 参数3:新数组长度
  • 函数的时间复杂度:创建一个长度为newLength的数组所需时间为Θ(1)。copy的时间复杂度为Θ(length)。所以,下面函数的总的时间复杂度为Θ(length)

 
 
  1. template< typename T>
  2. void changeLength(T* &a, int oldLength, int newLength)
  3. {
  4. if (newLength < 0) {
  5. throw illegalParameterValue( "new length must be >=0");
  6. }
  7. T *arr = new T[newLength];
  8. int length = min(oldLength, newLength);
  9. copy(a, a + length, arr);
  10. delete [] a;
  11. a = arr;
  12. }

线性表类实现

  • linearList是线性表的纯虚函数,其含有一系列线性表的操作方法;arrayList继承于linearList,是线性表的数组实现形式的类,其重写linearList的虚函数,并添加了自己的一些数据成员和方法

 
 
  1. template< typename T>
  2. class linearList
  3. {
  4. public:
  5. virtual ~linearList() {};
  6. //当线性表为空时返回true
  7. virtual bool empty() const = 0;
  8. //返回线性表的元素个数
  9. virtual int size() const = 0;
  10. //返回索引theIndex的元素
  11. virtual T& get(int theIndex)const = 0;
  12. //返回元素theElement第一次出现时的索引
  13. virtual int indexOf(const T& theElement) const = 0;
  14. //删除索引为theIndex的元素
  15. virtual void earse(int theIndex) = 0;
  16. //把theElement插入线性表中索引为theIndex的位置上
  17. virtual void insert(int theIndex, const T& theElement) = 0;
  18. //把线性表插入输出流out
  19. virtual void output(ostream& out)const = 0;
  20. };
  21. template< typename T>
  22. class arrayList : public linearList<T>
  23. {
  24. public:
  25. arrayList( int initialCapacity = 10); //构造函数
  26. arrayList( const arrayList<T>& other); //拷贝构造
  27. ~arrayList(); //析构函数
  28. bool empty()const override;
  29. int size()const override;
  30. T& get(int theIndex)const override;
  31. int indexOf(const T& theElement)const override;
  32. void earse(int theIndex)override;
  33. void insert(int theIndex, const T& theElement)override;
  34. void output(ostream& out)const override;
  35. //返回数组的容量
  36. int capacity()const;
  37. protected:
  38. T *element; //存储线性表的一维数组
  39. int arrayLength; //一位数组的容量
  40. int listSize; //当前线性表的元素个数
  41. protected:
  42. void checkIndex(int theIndex)const; //检查索引theIndex是否有效
  43. };

构造函数

  • 构造函数首先判断传入的数组长度是否有效,如果无效抛出一个异常。如果有效,初始化数组
  • 时间复杂度:
    • 如果T是基本数据类型,那么构造函数的时间复杂度是O(1)
    • 如果T是用户自定义类型,那么构造函数的时间复杂度是O(initialCapacity)。因为数组的每一个元素都是自定义类型,需要调用构造函数

 
 
  1. template< typename T>
  2. arrayList<T>::arrayList( int initialCapacity)
  3. {
  4. if (initialCapacity < 1) {
  5. ostringstream s;
  6. s << "Initial capacity is " << initialCapacity << ",Must be >0";
  7. throw illegalParameterValue(s.str().c_str());
  8. //str()将ostringstream转换为string,c_str()将string转换为const char*
  9. }
  10. arrayLength = initialCapacity;
  11. element = new T[arrayLength];
  12. listSize = 0;
  13. }

拷贝构造函数

  • 初始化一些数据成员,然后调用copy函数进行拷贝
  • 时间复杂度:为O(listSize),其中listSize是要复制的线性表的大小

 
 
  1. template< typename T>
  2. arrayList<T>::arrayList( const arrayList<T>& other)
  3. {
  4. arrayLength = other.arrayLength;
  5. listSize = other.listSize;
  6. element = new T[arrayLength];
  7. copy(other.element, other.element + listSize, element);
  8. }

析构函数


 
 
  1. template< typename T>
  2. arrayList<T>::~arrayList()
  3. {
  4. delete[] element;
  5. }

empty、size、capacity

  • 下面3个函数的时间复杂度都是O(1)

 
 
  1. template< typename T>
  2. bool arrayList<T>::empty() const
  3. {
  4. return listSize == 0;
  5. }
  6. template< typename T>
  7. int arrayList<T>::size() const
  8. {
  9. return listSize;
  10. }
  11. template< typename T>
  12. int arrayList<T>::capacity() const
  13. {
  14. return arrayLength;
  15. }

检索下标合格性(checkIndex)

  • 时间复杂度是Θ(1)

 
 
  1. template< typename T>
  2. void arrayList<T>::checkIndex( int theIndex) const
  3. {
  4. if ((theIndex< 0) || (theIndex>=arrayLength)) {
  5. ostringstream s;
  6. s << "index=" << theIndex << ",size=" << arrayLength;
  7. throw illegalParameterValue(s.str().c_str());
  8. }
  9. }

返回指定索引处值(get) 

  • 时间复杂度是Θ(1)

 
 
  1. template< typename T>
  2. T& arrayList<T>::get( int theIndex) const
  3. {
  4. checkIndex(theIndex);
  5. return element[theIndex];
  6. }

返回元素在线性表的指定位置(indexOf)

  • 时间复杂度是O(listsize)

 
 
  1. template< typename T>
  2. int arrayList<T>::indexOf( const T& theElement) const
  3. {
  4. int index;
  5. //find成功返回查找的迭代器位置,失败返回参数2
  6. index = ( int)(find(element, element + listSize, theElement) - element);
  7. //如果没有找到
  8. if (index == listSize) {
  9. return - 1;
  10. }
  11. else
  12. return index;
  13. }

删除指定索引处的元素(earse) 

  • 时间复杂度:
    <ul><li>如果checkIndex抛出异常,那么时间复杂度是Θ(1)</li>
    	<li>如果元素存在,那么要移动的元素个数是listSize-theIndex,所以时间复杂度是Θ(listSize-theIndex)(假设每一个元素移动的时间复杂度是O(1))</li>
    	<li>因此,全部时间复杂度是O(listSize-theIndex)</li>
    </ul></li>
    

 
 
  1. template< typename T>
  2. void arrayList<T>::earse( int theIndex)
  3. {
  4. checkIndex(theIndex);
  5. //参数12分别为要移动的区间的起始迭代位置和尾后迭代器
  6. copy(element+ theIndex+ 1, element+ listSize, element + theIndex);
  7. element[--listSize].~T();
  8. }

在指定索引处插入元素(insert)

  • 这里没有没有使用copy函数来移动元素,而是使用copy_backward来移动元素
  • 时间复杂度:
    • 如果抛出异常,那么时间复杂度是Θ(1)
    • 如果数组需要加长,那么时间复杂度是Θ(arrayLength)=Θ(listSize)
    • copy_backward移动数组元素,时间复杂度是Θ(listSize-theIndex)
    • 综上所述,下面函数的时间复杂度是O(listSize)

 
 
  1. template< typename T>
  2. void arrayList<T>::insert( int theIndex, const T& theElement)
  3. {
  4. if ((theIndex< 0) || (theIndex>listSize)) {
  5. ostringstream s;
  6. s << "index=" << theIndex << ",size=" << arrayLength;
  7. throw illegalParameterValue(s.str().c_str());
  8. }
  9. if (listSize == arrayLength) {
  10. changeLength(element, arrayLength, arrayLength * 2);
  11. arrayLength *= 2;
  12. }
  13. //会复制前两个迭代器参数指定的序列。第三个参数是目的序列的结束迭代器
  14. copy_backward(element+ theIndex, element+ listSize, element + listSize+ 1);
  15. element[theIndex] = theElement;
  16. listSize++;
  17. }

打印线性表元素(output)

  • 时间复杂度:如果插入一个元素的时间是O(1),那么这个代码的时间复杂度是O(listSize)

 
 
  1. template< typename T>
  2. void arrayList<T>::output(ostream& out) const
  3. {
  4. copy(element, element+ listSize,ostream_iterator<T>(out, " "));
  5. }

重载流插入符<<

  • 这个是全局函数

 
 
  1. template< typename T>
  2. ostream& operator<<(ostream& out, const arrayList<T>& x)
  3. {
  4. x.output(out);
  5. return out;
  6. }

改写earse函数

  • 为了能够在数组元素减少时释放一些数组空间,我们修改earse方法,当listSize<arrayLength/4时,将数组的长度减少到arrayLength/2

 
 
  1. template< typename T>
  2. void arrayList<T>::earse( int theIndex)
  3. {
  4. checkIndex(theIndex);
  5. copy(element+ theIndex+ 1, element+ listSize, element + theIndex);
  6. if (listSize < (arrayLength / 4)) {
  7. changeLength(element, arrayLength, arrayLength / 2);
  8. }
  9. element[--listSize].~T();
  10. }

演示效果


 
 
  1. int main()
  2. {
  3. linearList< int>* list = (linearList< int>*) new arrayList< int>();
  4. cout << "Current size:"<< list->size()<< "\n"<< endl;
  5. cout << "Current list is:";
  6. list->output( cout);
  7. list->insert( 0, 1);
  8. list->insert( 0, 2);
  9. cout << "Current size:" << list->size() << "\n" << endl;
  10. cout << "Current list is:";
  11. list->output( cout);
  12. cout << "The index of 2 is:" << list->indexOf( 2)<< "\n"<< endl;
  13. list->earse( 1);
  14. cout << "Current list is:";
  15. list->output( cout);
  16. return 0;
  17. }

  • 备注:此处我们使用抽象类linearList指向与派生类arrayList,所以不能使用基类的capacity()函数

四、迭代器的设计

  • 我们定义了一个双向迭代器。这个迭代器是类arrayList的公有成员,此外我们还为arrayList定义了两个公有方法begin()和end()

 
 
  1. template< typename T>
  2. class arrayList : public linearList<T>
  3. {
  4. /*...*/
  5. public:
  6. class iterator;
  7. iterator begin() {
  8. return iterator(element);
  9. }
  10. iterator end() {
  11. return iterator(element + listSize);
  12. }
  13. class iterator
  14. {
  15. public:
  16. typedef bidirectional_iterator_tag iterator_category;
  17. typedef T value_type;
  18. typedef ptrdiff_t difference_type;
  19. typedef T* pointer;
  20. typedef T& reference;
  21. public:
  22. iterator(T* thePosition = 0) {
  23. position = thePosition;
  24. }
  25. /*~iterator() {
  26. //析构函数出错,不知道为什么,待续
  27. if (position) {
  28. delete[] position;
  29. position = nullptr;
  30. }
  31. }*/
  32. T& operator*() const {
  33. return *position;
  34. }
  35. T* operator->() const {
  36. return &*position;
  37. }
  38. iterator& operator++() {
  39. ++position;
  40. return * this;
  41. }
  42. iterator operator++( int) {
  43. iterator old = * this;
  44. ++position;
  45. return old;
  46. }
  47. iterator& operator--() {
  48. --position;
  49. return * this;
  50. }
  51. iterator operator--( int) {
  52. iterator old = * this;
  53. --position;
  54. return old;
  55. }
  56. bool operator==( const iterator other) const {
  57. return position == other.position;
  58. }
  59. bool operator!=( const iterator other) const {
  60. return position != other.position;
  61. }
  62. protected:
  63. T *position;
  64. };
  65. /*...*/
  66. };

演示案例


 
 
  1. int main()
  2. {
  3. arrayList< int> list;
  4. list.insert( 0, 1);
  5. list.insert( 1, 2);
  6. list.insert( 2, 3);
  7. list.insert( 3, 4);
  8. arrayList< int>::iterator iter;
  9. for (iter = list.begin(); iter != list.end(); ++iter) {
  10. cout <<*iter << " ";
  11. }
  12. cout << "\n" << endl;
  13. return 0;
  14. }

五、总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值