数组
数组是一种线性表数据结构。它用一组连续内存空间,来存储一组具有相同数据类型数据。
1.线性表:数据存储像一条线一样的结构,每个线性表上的数据最多只有前和后的两个方向,如数组、链表、队列、栈等都是这种结构,所以实现的数组的动态操作,其他结构也可轻易的类似实现。更重要的是,在这之后看源码就可大大降低难度。(博主自己看的是STL源码剖析)
2.非线性表:如二叉树、堆、图等。
3连续内存空间和相同数据类型:当数组作插入、删除操作时,为了保证数据的连续性,往往需要做大量的数据搬移工作,效率很低。
动态数组功能实现
1.数组初始化
考虑到扩容时数据搬移可能会发生的内存泄露,博主这里采用两只手的原则,即设定一个内存标志位 ItemsFlag 。当 ItemsFlag = 0,using preitems;当 ItemsFlag = 1,using items。下文扩容部分有具体实现。默认数组容量10。
1
2
3
4
5
6
7
8
9
|
enum { MAX = 10 }; explicit GenericArray( int ss = MAX); template < class T> GenericArray<T>::GenericArray( int ss) : capacity(ss),counts(0) { itemsFlag = 0; preitems = new T[capacity]; items = nullptr; } |
2.析构函数
释放内存。
1
2
3
4
5
6
7
8
|
template < class T> GenericArray<T>::~GenericArray() { if (preitems != nullptr) delete []preitems; if (items != nullptr) delete []items; } |
3.检查下标
检查要操作的下标是否在数组容量范围内。
1
2
3
4
5
6
7
8
9
10
11
12
|
template < class T> bool GenericArray<T>::checkIndex( int index) { if (index < 0 || index >= capacity) { int cap = capacity - 1; cout << "Out of the range! Please ensure the index be in 0 ~ " << cap << '\n' ; return false ; } return true ; } |
4.获取元素数目和容量、判断数组空和满
1
2
3
4
|
int count() const { return counts; } int getCapacity() const { return capacity; } bool isEmpty() const { return counts == 0; } bool isFull() const { return counts >= capacity; } |
5.取索引对应值、按索引修改值、打印输出、是否包含某值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
template < class T> T GenericArray<T>::get( int index) { if (!itemsFlag) return preitems[index]; else return items[index]; } void GenericArray<T>::set( int index, T elem) { if (checkIndex(index)) { if (!itemsFlag) preitems[index] = elem; else items[index] = elem; return ; } } template < class T> void GenericArray<T>::printArray() const { for ( int i = 0; i < counts; i++) if (!itemsFlag) cout << preitems[i] << '\t' ; else cout << items[i] << '\t' ; cout << '\n' ; return ; } template < class T> bool GenericArray<T>::contains(T arr) { for ( int i = counts - 1; i >= 0; i--) if (!itemsFlag) { if (arr == preitems[i]) return true ; } else { if (arr == items[i]) return true ; } return false ; } |
6.查找某值下标、删除某值
查找某值的下标时,要考虑到该值在数组中是否重复,所以博主用了一个结构体 findArrIndex 来存储该值重复的次数和对应的下标。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
struct findArrIndex { int numIndex; int *findIndex; }; template < class T> void GenericArray<T>::find(T arr, findArrIndex *ps) { ps->findIndex = new int [counts]; ps->numIndex = 0; for ( int i = 0, j = 0; i < counts; i++, j++) if (!itemsFlag) { if (arr == preitems[i]) { (ps->findIndex)[j] = i; (*ps).numIndex++; cout << i << '\t' ; } } else if (arr == items[i]) { (ps->findIndex)[j] = i; (*ps).numIndex++; cout << i << '\t' ; } cout << '\n' ; return ; } template < class T> void GenericArray<T>::removeElement(findArrIndex *ps) { for ( int i = ps->numIndex; i > 0; i--) remove ((ps->findIndex)[i - 1]); delete [](ps->findIndex); } template < class T> void GenericArray<T>::set( int index, T elem) { if (checkIndex(index)) { if (!itemsFlag) preitems[index] = elem; else items[index] = elem; return ; } } |
7.扩容
添加数据操作时需判断数组容量是否足够,若不够需考虑扩容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
template < class T> void GenericArray<T>::renewCapacity() { cout << "The array's capacity is small! Renew capacity.\n" ; if (capacity < 1000) capacity = capacity << 1; else capacity = capacity >> 1 + capacity; if (!itemsFlag) { itemsFlag = 1; items = new T[capacity]; for ( int i = 0; i<counts; i++) *(items + i) = *(preitems + i); //items[i]=proitems[i]; //cout << items << '\n'; //cout << preitems << '\n'; delete []preitems; preitems = nullptr; } else { itemsFlag = 0; preitems = new T[capacity]; for ( int i = 0; i<counts; i++) *(preitems + i) = *(items + i); delete []items; items = nullptr; } } |
8.添加数据:数组添加数据包括按索引下标插值、数组头插值、数组尾插值。实质上后两种都可以通过调用按索引下标插值函数实现。前文也提到过,数组添加操作中复杂的是大量的数据搬移工作:将某个元素按索引下标插入到数组第k个位置,需要将k ~ n部分的元素向后搬移一位,然后插入元素,更新元素数目。若插入到数组尾,时间复杂度O(1);插入到数组头,时间复杂度O(n);插入的平均时间复杂度为(1+2+…+n)/n = O(n)。
另外,还有一个优化问题:若数组是无序数组,则插入时不需要搬移数据:若将某个元素插入到数组第k个位置,首先将该位置的元素移动到数组末尾,然后将待插入元素插入到第k个位置,时间复杂度O(1)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
template < class T> void GenericArray<T>::add( int index, T elem) { if (isFull()) { cout << "Array is full!" << '\n' ; renewCapacity(); } if (checkIndex(index)) if (!itemsFlag) { for ( int i = counts; i > index; i--) preitems[i] = preitems[i - 1]; preitems[index] = elem; } else { for ( int i = counts; i > index; i--) items[i] = items[i - 1]; items[index] = elem; } counts++; return ; } template < class T> void GenericArray<T>::addFirst(T elem) { add(0, elem); } template < class T> void GenericArray<T>::addLast(T elem) { add(counts, elem); } |
9.删除数据:数组删除数据包括按索引下标删除、数组头删除、数组尾删除。实质上后两种都可以通过调用按索引下标删除函数实现。与前文类似,数组删除操作中复杂的也是大量的数据搬移工作:按索引下标将某个元素删除,需要将k+1 ~ n部分的元素向前搬移一位,更新元素数目。若删除数组尾,直接元素数目减一即可,时间复杂度O(1);删除数组头,时间复杂度O(n);删除的平均时间复杂度(1+2+…+n)/n = O(n)。
另外,有一个优化问题:如果想多次删除数组中的值,可以先对要删除的值做好标记,做完标记后一次删除,这样就大大减少了搬移的次数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
template < class T> T GenericArray<T>:: remove ( int index) { if (!isEmpty()) { if (checkIndex(index)) { if (!itemsFlag) { T temp = preitems[index]; for ( int i = index+1; i < counts; i++) preitems[i - 1] = preitems[i]; counts--; return temp; } else { T temp = items[index]; for ( int i = index + 1; i < counts; i++) items[i - 1] = items[i]; counts--; return temp; } } } else { cout << "Array is empty!" << '\n' ; return -1; } } template < class T> T GenericArray<T>::removeFirst() { return remove (0); } template < class T> T GenericArray<T>::removeLast() { return remove (counts - 1); } |
好啦,基本上就这么多了。
最后总结一下,多看源码还是很重要的。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/weixin_43428242/article/details/83989513