【数据结构】Python中的顺序表——List

大学必修课

《数据结构与算法》是计算机一门必修课,不管是在哪个大学。记得当时学这门课程是还是有C语言实现的,接触到的第一种数据结构是线性表中的顺序表,是使用数组实现,结构代码如下:

#define MAXSIZE 20
typedef int ElemType;
typedef struct
{
     
	ElemType data[MAXSIZE];
	int length ;//顺序表当前长度
}Sqlist;

这里封装了一个结构,其实是对数据进行封装,数据的最大长度定义为MAXSIZE,另外增加一个记录数据当前长度的变量。

顺序表最常用的两个操作是插入和删除,实现的代码网上都有。不过对于这种静态的顺序表有个问题,就是一旦达到最大的长度就不能再进行数据插入,所以还有一种动态的顺序表结构;

typedef int DataType;
typedef struct SeqList
{
     
    DataType* _a;
    size_t _size; // 有效数据个数 
    size_t _capacity; // 容量 
}SeqList;

这里就用到了C语言的精髓——指针,简单来说就是你可以动态扩容。具体实现参考 https://blog.csdn.net/qq_34772530/article/details/78804329

人生苦短,我用 Python

用过Pyhton的道友们,都对“人生苦短,我用 Python”这句话深有体会。
【数据结构】Python中的顺序表——List_第1张图片
Python中也有对应的顺序表结构——List,对应的方法都已经封装好,虽然其底层还是有C实现,但是直接使用就是舒服。
不过有时也会很好奇其如何定义内部结构?如何实现动态地插入扩展?
So,上GitHub找了源码,再结合《python源码剖析》还有其他博主分享,记录下理解到的内容。

结构定义

cpython-master\Include\cpython\listobject.h

typedef struct {
     
    PyObject_VAR_HEAD 
    /* Vector of pointers to list elements.  list[0] is ob_item[0], etc. */
    PyObject **ob_item;
    /* ob_item contains space for 'allocated' elements.  The number
     * currently in use is ob_size.
     * Invariants:
     *     0 <= ob_size <= allocated
     *     len(list) == ob_size
     *     ob_item == NULL implies ob_size == allocated == 0
     * list.sort() temporarily sets allocated to -1 to detect mutations.
     *
     * Items must normally not be NULL, except during construction when
     * the list is not yet visible outside the function that builds it.
     */
    Py_ssize_t allocated;
} PyListObject;

在cpython-master\Include\object.h中有对PyObject_VAR_HEAD的定义

/* PyObject_VAR_HEAD defines the initial segment of all variable-size
 * container objects.  These end with a declaration of an array with 1
 * element, but enough space is malloc'ed so that the array actually
 * has room for ob_size elements.  Note that ob_size is an element count,
 * not necessarily a byte count.
 */

PyObject_VAR_HEAD 中的ob_size记录着当前列表中的元素个数;
PyObject **ob_item 则是指向列表所在内存块首地址的指针;
allocated 记录了这个列表申请的内存大小;
ob_size 和 allocated 关系如下

  0 <= ob_size <= allocated
  len(list) == ob_size
  ob_item == NULL 则意味着 ob_size == allocated == 0
列表创建

cpython-master\Include\cpython\listobject.h

PyObject *
PyList_New(Py_ssize_t size)
{
     
    //1, 参数检查 列表大小不能是 0
	if (size < 0) {
     
        PyErr_BadInternalCall();
        return NULL;
    }

    struct _Py_list_state *state = get_list_state();
    PyListObject *op;
#ifdef Py_DEBUG
    // PyList_New() must not be called after _PyList_Fini()
    assert(state->numfree != -1);
#endif
    //2,为PyListObject对象申请空间
    if (state->numfree) {
     
        state->numfree--;
        op = state->free_list[state->numfree];
        _Py_NewReference((PyObject *)op);
    }
    else {
     
        op = PyObject_GC_New(PyListObject, &PyList_Type);
        if (op == NULL) {
     
            return NULL;
        }
    }
	//3,为PyListObject对象中的元素申请空间
    if (size <= 0) {
     
        op->ob_item = NULL;
    }
    else {
     
        op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *));
        if (op->ob_item == NULL) {
     
            Py_DECREF(op);
            return PyErr_NoMemory();
        }
    }
	//4,维护allocated和ob_size
    Py_SET_SIZE(op, size);
    op->allocated = size;
    _PyObject_GC_TRACK(op);
    return (PyObject *) op;
}

列表创建主要有以下过程:
1,参数检查 列表大小不能是 0
2,创建新的PyListObject对象,这里使用到对象级缓冲池技术(不懂),
检查free_list中是否有可用对象,如果有,直接使用可用对象;
如果缓冲池所有对象都不可以使用,则通过PyObject_GC_New在系统堆申请内存,创建新对象。
3,创建新的PyListObject对象后,立即根据size参数创建PyListObject对象维护的列表元素。
4,完成PyListObject对象和列表的创建后,维护allocated和ob_size

赋值操作

cpython-master\Include\cpython\listobject.h

int
PyList_SetItem(PyObject *op, Py_ssize_t i,
               PyObject *newitem)
{
     
    PyObject **p;
    if (!PyList_Check(op)) {
     
        Py_XDECREF(newitem);
        PyErr_BadInternalCall();
        return -1;
    }
    if (!valid_index(i, Py_SIZE(op))) {
     
        Py_XDECREF(newitem);
        PyErr_SetString(PyExc_IndexError,
                        "list assignment index out of range");
        return -1;
    }
    p = ((PyListObject *)op) -> ob_item + i;
    Py_XSETREF(*p, newitem);
    return 0;
}

赋值操作主要有以下过程
1,参数类型检查
2,索引有效性检查,不可超出索引
3,元素赋值

插入元素

cpython-master\Include\cpython\listobject.h
插入元素 使用的是ins1(PyListObject *self, Py_ssize_t where, PyObject *v) 方法来实现

static int
ins1(PyListObject *self, Py_ssize_t where, PyObject *v)
{
     
    Py_ssize_t i, n = Py_SIZE(self);
    PyObject **items;
	//1,参数检查
    if (v == NULL) {
     
        PyErr_BadInternalCall();
        return -1;
    }
	
	//2,列表容量调整
    assert((size_t)n + 1 < PY_SSIZE_T_MAX);
    if (list_resize(self, n+1) < 0)
        return -1;
	
	//3,确定插入点
    if (where < 0) {
     
        where += n;
        if (where < 0)
            where = 0;
    }
    if (where > n)
        where = n;
	//4,插入元素
    items = self->ob_item;
    for (i = n; --i >= where; )
        items[i+1] = items[i];
    Py_INCREF(v);
    items[where] = v;
    return 0;
}

插入元素主要有以下过程
1,参数检查
2,列表容量调整,使用list_resize函数维护容量,其中有算法来判断是否需要申请内存扩展列表,具体实现可以研究下这个函数
3,确定插入点,由于List有负数索引,所以必须处理负数的情形。
4,确定插入位置后,开始挪动元素,将插入点之后的元素向后移动一位,这样插入点就空出一个位置。

删除元素

cpython-master\Include\cpython\listobject.h
删除元素使用的是list_remove(PyListObject *self, PyObject *value) 方法来实现

static PyObject *
list_remove(PyListObject *self, PyObject *value)
/*[clinic end generated code: output=f087e1951a5e30d1 input=2dc2ba5bb2fb1f82]*/
{
     
    Py_ssize_t i;

    for (i = 0; i < Py_SIZE(self); i++) {
     
        PyObject *obj = self->ob_item[i];
        Py_INCREF(obj);
        int cmp = PyObject_RichCompareBool(obj, value, Py_EQ);
        Py_DECREF(obj);
        if (cmp > 0) {
     
            if (list_ass_slice(self, i, i+1,
                               (PyObject *)NULL) == 0)
                Py_RETURN_NONE;
            return NULL;
        }
        else if (cmp < 0)
            return NULL;
    }
    PyErr_SetString(PyExc_ValueError, "list.remove(x): x not in list");
    return NULL;
}

删除元素主要有以下过程
1,对整个列表进行遍历,将待删除的元素与PyListObject中的每个元素进行比较,如果匹配到则调用
list_ass_slice函数。
2,list_ass_slice根据v是否为NULL,判断是replace还是delete;
例如
l = [1,2,3,4]
ilow = 1,ihigh=3,v=[‘a’,‘b’]
l = [1,‘a’,‘b’,‘4’]
ilow = 0,ihigh=2,v=[]
l = [‘b’,‘4’]
list_ass_slice的实现看了好复杂,也没看明白,感兴趣的可以研究一波,不过感觉C要很熟悉才看的明白。

cpython-master\Include\cpython\listobject.h中还有Lits其他方法的具体实现,有兴趣可以研究一波。Pyhton虽然很强大,但是感觉C语言才是真正的强大。

参考:
《python源码剖析》
https://blog.csdn.net/lucky404/article/details/79596319

你可能感兴趣的:(Python,数据结构,python,顺序表,List)