Learning notes of data structure, algorithm and application - C + + language description - linear table

Posted by beboo002 on Fri, 21 Jan 2022 02:46:20 +0100

1, Linear meter interface specification

Linear tables generally need to implement the following interfaces:

#pragma once
#include <iostream>
template<class T>
class linearList
{
public:
	virtual ~linearList() {};
	virtual bool empty() = 0;
	virtual int size() const = 0;
	virtual T& get(int index) const = 0;
	virtual int indexOf(const T& element) const = 0;
	virtual void erase(int index) = 0;
	virtual void insert(int index, const T& element) = 0;
	virtual void output(std::ostream& out) const = 0;
};

2, Linear table array description

We can use the array interface to realize the function of linear table:

#include "linearList.h"
using namespace std;

template<class T>
class arrayList : public linearList<T>
{
public:
	arrayList(int capacity = 10);
	arrayList(const arrayList<T>& other);
	~arrayList();

	bool empty();
	int size() const;
	T& get(int index) const;
	int indexOf(const T& element) const;
	void erase(int index);
	void insert(int index, const T& val);
	void output(std::ostream& out) const;

	int capacity() const;

private:
	void checkIndex(int index) const;

	T* element;
	int arrayLength;
	int listSize;
};

Here, the function for array subscript checking is not required by the C + + standard and can be implemented.

1. Tectonics and Deconstruction

template<class T>
arrayList<T>::arrayList(int capacity)
{
	if (capacity < 1)
	{
		throw new invalid_argument(string("Initial Capacity = %d must be > 0", capacity));
	}

	arrayLength = capacity;
	listSize = 0;
	element = new T[capacity];
}

template<class T>
arrayList<T>::arrayList(const arrayList<T>& other)
{
	T* pTmp = new T[other.arrayLength];
	copy(other.element, other.element + other.arrayLength, pTmp);

	element = pTmp;
	arrayLength = other.arrayLength;
	listSize = other.listSize;
}

template<class T>
arrayList<T>::~arrayList()
{
	delete[] element;
}

The time complexity of the constructor depends on the type of template parameter T. When it is a basic type, the time complexity of the new operator is O(1); When it is a custom type, each new element in the array needs to call the corresponding constructor, so the complexity is O(capacity).
In copy construction, we should pay attention to the order of memory allocation, copy and array length update, because we need to consider the exception of new. The time complexity of the copy constructor is O(n), which is proportional to the number of elements in the target linear table;

2. Capacity

template<class T>
bool arrayList<T>::empty()
{
	return listSize == 0;
}

template<class T>
int arrayList<T>::size() const
{
	return listSize;
}

template<class T>
int arrayList<T>::capacity() const
{
	return arrayLength;
}

The time complexity of these methods is O(1).

3. Search

template<class T>
void arrayList<T>::checkIndex(int index) const
{
	if (index >= listSize || index < 0)
	{
		throw new out_of_range(string("index = %d out of range with size = %d", index, listSize));
	}
}

template<class T>
T& arrayList<T>::get(int index) const
{
	checkIndex(index);
	return element[index];
}

template<class T>
int arrayList<T>::indexOf(const T& val) const
{
	auto ptr = find(element, element + listSize, val);
	if (ptr >= element + listSize || ptr < element)
	{
		return -1;
	}

	return ptr - element;
}

The time complexity of element lookup is O(listSize).

4. Delete

template<class T>
void arrayList<T>::erase(int index)
{
	checkIndex(index);
	copy(element + index + 1, element + listSize, element + index);
	element[--listSize].~T();
}

5. Insert

When the array needs to be expanded, the insertion only supports the expansion of one element, but does not support the insertion at any specified position.

template<class T>
void changeLength1D(T*& ori, int oldLength, int newLength)
{
	if (newLength < 0)
	{
		throw new invalid_argument("new length must be > 0");
	}

	T* pTmp = new T[newLength];
	copy(ori, ori + min(oldLength, newLength), pTmp);
	delete[] ori;
	ori = pTmp;
}

template<class T>
void arrayList<T>::insert(int index, const T& val)
{
	if (index > listSize || index < 0)
	{
		throw new out_of_range(string("index = %d out of range with size = %d", index, listSize));
	}

	if (listSize == arrayLength)
	{
		changeLength1D(element, arrayLength, arrayLength * 2);
		arrayLength *= 2;
	}

	copy_backward(element + index, element + listSize, element + listSize + 1);
	element[index] = val;
	listSize++;
}

Here, we double the capacity of the array every time we expand, so that we can avoid the continuous expansion of the array in the case of frequent insertion. The time complexity of this method is O(listSize).

6. Output

template<class T>
std::ostream& operator<<(std::ostream& out,const arrayList<T>& arr)
{
	arr.output(out);
	return out;
}

template<class T>
void arrayList<T>::output(ostream& out) const
{
	copy(element, element + listSize, ostream_iterator<T>(out, " "));
}

7. Volume reduction

Expansion is necessary when space is insufficient. Many times, in order to make full use of space, we will shrink the volume when deleting elements.

3, Iterator

The iterator is the access unit of STL algorithm. Many general algorithms reuse code through iterators. Linear table requires that iterators can be moved forward and backward to traverse, so we develop a bidirectional iterator for arrayList class (in order to meet the requirements of type_traits in STL, we need to define some type aliases for this class).

1. Interface

The structure of the iterator is as follows:

#pragma once
template<class T>
class arrayListIterator
{
public:
	using iterator_category = std::bidirectional_iterator_tag;
	using value_type = T;
	using difference_type = ptrdiff_t;
	using pointer = T*;
	using reference = const value_type&;

	arrayListIterator(T* _position);
	T& operator*() const;
	T* operator->() const;
	arrayListIterator<T> operator++();
	arrayListIterator<T> operator++(int);
	arrayListIterator<T> operator--();
	arrayListIterator<T> operator--(int);
	bool operator==(const arrayListIterator<T>& _right) const;
	bool operator!=(const arrayListIterator<T>& _right) const;

protected:
	T* position;
};

2. Realize

template<class T>
inline arrayListIterator<T>::arrayListIterator(T* _position)
{
	position = _position;
}

template<class T>
inline T& arrayListIterator<T>::operator*() const
{
	return *position;
}

template<class T>
inline T* arrayListIterator<T>::operator->() const
{
	return position;
}

template<class T>
inline arrayListIterator<T> arrayListIterator<T>::operator++()
{
	position++;
	return *this;
}

template<class T>
inline arrayListIterator<T> arrayListIterator<T>::operator++(int)
{
	arrayListIterator<T> old = *this;
	position++;
	return old;
}

template<class T>
inline arrayListIterator<T> arrayListIterator<T>::operator--()
{
	position--;
	return *this;
}

template<class T>
inline arrayListIterator<T> arrayListIterator<T>::operator--(int)
{
	arrayListIterator<T> old = *this;
	position--;
	return old;
}

template<class T>
inline bool arrayListIterator<T>::operator==(const arrayListIterator<T>& _right) const
{
	return position == _right.position;
}

template<class T>
inline bool arrayListIterator<T>::operator!=(const arrayListIterator<T>& _right) const
{
	return position != _right.position;
}

3. begin and end

In the linear table, we need to provide begin and end interfaces to access array elements:

class arrayList
{
	iterator begin();
	iterator end();
}

template<class T>
inline arrayListIterator<T> arrayList<T>::begin()
{
	return arrayList<T>::iterator(element);
}

template<class T>
inline arrayListIterator<T> arrayList<T>::end()
{
	return arrayList<T>::iterator(element + size());
}

Topics: C++ Algorithm data structure